blob: 453a02566a3a44b46102b8e5e5ca52522fb57ca3 [file] [log] [blame]
"""Utilities for verifying git integrity."""
# Used also from setup.py, so don't pull in anything additional here (like mypy or typing):
import os
import pipes
import subprocess
import sys
MYPY = False
if MYPY:
from typing import Iterator
def is_git_repo(dir: str) -> bool:
"""Is the given directory version-controlled with git?"""
return os.path.exists(os.path.join(dir, ".git"))
def have_git() -> bool:
"""Can we run the git executable?"""
try:
subprocess.check_output(["git", "--help"])
return True
except subprocess.CalledProcessError:
return False
except OSError:
return False
def get_submodules(dir: str) -> "Iterator[str]":
"""Return a list of all git top-level submodules in a given directory."""
# It would be nicer to do
# "git submodule foreach 'echo MODULE $name $path $sha1 $toplevel'"
# but that wouldn't work on Windows.
output = subprocess.check_output(["git", "submodule", "status"], cwd=dir)
# "<status><sha1> name desc"
# status='-': not initialized
# status='+': changed
# status='u': merge conflicts
# status=' ': up-to-date
for line in output.splitlines():
# Skip the status indicator, as it could be a space can confuse the split.
line = line[1:]
name = line.split(b" ")[1]
yield name.decode(sys.getfilesystemencoding())
def git_revision(dir: str) -> bytes:
"""Get the SHA-1 of the HEAD of a git repository."""
return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=dir).strip()
def submodule_revision(dir: str, submodule: str) -> bytes:
"""Get the SHA-1 a submodule is supposed to have."""
output = subprocess.check_output(["git", "ls-files", "-s", submodule], cwd=dir).strip()
# E.g.: "160000 e4a7edb949e0b920b16f61aeeb19fc3d328f3012 0 typeshed"
return output.split()[1]
def is_dirty(dir: str) -> bool:
"""Check whether a git repository has uncommitted changes."""
output = subprocess.check_output(["git", "status", "-uno", "--porcelain"], cwd=dir)
return output.strip() != b""
def has_extra_files(dir: str) -> bool:
"""Check whether a git repository has untracked files."""
output = subprocess.check_output(["git", "clean", "--dry-run", "-d"], cwd=dir)
return output.strip() != b""
def warn_no_git_executable() -> None:
print("Warning: Couldn't check git integrity. "
"git executable not in path.", file=sys.stderr)
def warn_dirty(dir: str) -> None:
print("Warning: git module '{}' has uncommitted changes.".format(dir),
file=sys.stderr)
print("Go to the directory", file=sys.stderr)
print(" {}".format(dir), file=sys.stderr)
print("and commit or reset your changes", file=sys.stderr)
def warn_extra_files(dir: str) -> None:
print("Warning: git module '{}' has untracked files.".format(dir),
file=sys.stderr)
print("Go to the directory", file=sys.stderr)
print(" {}".format(dir), file=sys.stderr)
print("and add & commit your new files.", file=sys.stderr)
def chdir_prefix(dir: str) -> str:
"""Return the command to change to the target directory, plus '&&'."""
if os.path.relpath(dir) != ".":
return "cd " + pipes.quote(dir) + " && "
else:
return ""
def error_submodule_not_initialized(name: str, dir: str) -> None:
print("Submodule '{}' not initialized.".format(name), file=sys.stderr)
print("Please run:", file=sys.stderr)
print(" {}git submodule update --init {}".format(
chdir_prefix(dir), name), file=sys.stderr)
def error_submodule_not_updated(name: str, dir: str) -> None:
print("Submodule '{}' not updated.".format(name), file=sys.stderr)
print("Please run:", file=sys.stderr)
print(" {}git submodule update {}".format(
chdir_prefix(dir), name), file=sys.stderr)
print("(If you got this message because you updated {} yourself".format(name), file=sys.stderr)
print(" then run \"git add {}\" to silence this check)".format(name), file=sys.stderr)
def verify_git_integrity_or_abort(datadir: str) -> None:
"""Verify the (submodule) integrity of a git repository.
Potentially output warnings/errors (to stderr), and exit with status 1
if we detected a severe problem.
"""
datadir = datadir or '.'
if not is_git_repo(datadir):
return
if not have_git():
warn_no_git_executable()
return
for submodule in get_submodules(datadir):
submodule_path = os.path.join(datadir, submodule)
if not is_git_repo(submodule_path):
error_submodule_not_initialized(submodule, datadir)
sys.exit(1)
elif submodule_revision(datadir, submodule) != git_revision(submodule_path):
error_submodule_not_updated(submodule, datadir)
sys.exit(1)
elif is_dirty(submodule_path):
warn_dirty(submodule)
elif has_extra_files(submodule_path):
warn_extra_files(submodule)