Coverage for tsfpga/git_utils.py: 96%
49 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project.
5# https://tsfpga.com
6# https://gitlab.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9import os
10from pathlib import Path
12from tsfpga.system_utils import file_is_in_directory
15def get_git_commit(directory):
16 """
17 Get a string describing the current git commit.
18 E.g. ``"abcdef0123"`` or ``"12345678 (local changes present)"``.
20 Arguments:
21 directory (pathlib.Path): The directory where git commands will be run.
23 Returns:
24 str: Git commit information.
25 """
26 git_commit = get_git_sha(directory=directory)
27 if git_local_changes_present(directory=directory):
28 git_commit += " (local changes present)"
30 return git_commit
33def get_git_sha(directory):
34 """
35 Get a short git SHA.
37 Arguments:
38 directory (pathlib.Path): The directory where git commands will be run.
40 Returns:
41 str: The SHA.
42 """
43 # Generally, eight to ten characters are more than enough to be unique within a project.
44 # The linux kernel, one of the largest projects, needs 11.
45 # https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection#Short-SHA-1
46 sha_length = 16
48 if "GIT_COMMIT" in os.environ:
49 return os.environ["GIT_COMMIT"][0:sha_length]
51 # Import fails if "git" executable is not available, hence it can not be on top level.
52 # This function should only be called if git is available.
53 # pylint: disable=import-outside-toplevel
54 from git import Repo
56 repo = Repo(directory, search_parent_directories=True)
57 git_sha = repo.head.commit.hexsha[0:sha_length]
59 return git_sha
62def git_local_changes_present(directory):
63 """
64 Check if the git repo has local changes.
66 Arguments:
67 directory (pathlib.Path): The directory where git commands will be run.
69 Returns:
70 bool: ``True`` if the repo contains changes that have been made after the last commit.
71 """
72 # Import fails if "git" executable is not available, hence it can not be on top level.
73 # This function should only be called if git is available.
74 # pylint: disable=import-outside-toplevel
75 from git import Repo
77 repo = Repo(directory, search_parent_directories=True)
79 return repo.is_dirty()
82def git_commands_are_available(directory):
83 """
84 True if "git" command executable is available, and ``directory`` is in a valid git repo.
85 """
86 try:
87 # pylint: disable=import-outside-toplevel
88 from git import Repo, InvalidGitRepositoryError
89 except ImportError:
90 return False
92 try:
93 Repo(directory, search_parent_directories=True)
94 except InvalidGitRepositoryError:
95 return False
97 return True
100def find_git_files(
101 directory,
102 exclude_directories=None,
103 file_endings_include=None,
104 file_endings_avoid=None,
105):
106 """
107 Find files that are checked in to git.
109 Arguments:
110 directory (pathlib.Path): Search in this directory.
111 exclude_directories (list(pathlib.Path)): Files in these directories will not be included.
112 file_endings_include (str or tuple(str)). Only files with these endings will be included.
113 file_endings_avoid (str or tuple(str)): String or tuple of strings. Files with these endings
114 will not be included.
116 Returns:
117 list(pathlib.Path): The files that are available in git.
118 """
120 # Import fails if "git" executable is not available, hence it can not be on top level.
121 # This function should only be called if git is available.
122 # pylint: disable=import-outside-toplevel
123 from git import Repo
125 exclude_directories = (
126 []
127 if exclude_directories is None
128 else [exclude_directory.resolve() for exclude_directory in exclude_directories]
129 )
131 def list_paths(root_tree, path):
132 for blob in root_tree.blobs:
133 yield path / blob.name
134 for tree in root_tree.trees:
135 yield from list_paths(tree, path / tree.name)
137 repo = Repo(directory, search_parent_directories=True)
138 repo_root = Path(repo.git_dir).parent.resolve()
140 for file_path in list_paths(repo.tree(), repo_root):
141 if file_endings_include is not None and not file_path.name.endswith(file_endings_include):
142 continue
144 if file_endings_avoid is not None and file_path.name.endswith(file_endings_avoid):
145 continue
147 if file_is_in_directory(file_path, exclude_directories):
148 continue
150 if file_is_in_directory(file_path, [directory]):
151 yield file_path