Coverage for tsfpga/git_utils.py: 96%

49 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-29 20:01 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the tsfpga project, a project platform for modern FPGA development. 

5# https://tsfpga.com 

6# https://gitlab.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9# Standard libraries 

10import os 

11from pathlib import Path 

12 

13# First party libraries 

14from tsfpga.system_utils import file_is_in_directory 

15 

16 

17def get_git_commit(directory): 

18 """ 

19 Get a string describing the current git commit. 

20 E.g. ``"abcdef0123"`` or ``"12345678 (local changes present)"``. 

21 

22 Arguments: 

23 directory (pathlib.Path): The directory where git commands will be run. 

24 

25 Returns: 

26 str: Git commit information. 

27 """ 

28 git_commit = get_git_sha(directory=directory) 

29 if git_local_changes_present(directory=directory): 

30 git_commit += " (local changes present)" 

31 

32 return git_commit 

33 

34 

35def get_git_sha(directory): 

36 """ 

37 Get a short git SHA. 

38 

39 Arguments: 

40 directory (pathlib.Path): The directory where git commands will be run. 

41 

42 Returns: 

43 str: The SHA. 

44 """ 

45 # Generally, eight to ten characters are more than enough to be unique within a project. 

46 # The linux kernel, one of the largest projects, needs 11. 

47 # https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection#Short-SHA-1 

48 sha_length = 16 

49 

50 if "GIT_COMMIT" in os.environ: 

51 return os.environ["GIT_COMMIT"][0:sha_length] 

52 

53 # Import fails if "git" executable is not available, hence it can not be on top level. 

54 # This function should only be called if git is available. 

55 # pylint: disable=import-outside-toplevel 

56 # Third party libraries 

57 from git import Repo 

58 

59 repo = Repo(directory, search_parent_directories=True) 

60 git_sha = repo.head.commit.hexsha[0:sha_length] 

61 

62 return git_sha 

63 

64 

65def git_local_changes_present(directory): 

66 """ 

67 Check if the git repo has local changes. 

68 

69 Arguments: 

70 directory (pathlib.Path): The directory where git commands will be run. 

71 

72 Returns: 

73 bool: ``True`` if the repo contains changes that have been made after the last commit. 

74 """ 

75 # Import fails if "git" executable is not available, hence it can not be on top level. 

76 # This function should only be called if git is available. 

77 # pylint: disable=import-outside-toplevel 

78 # Third party libraries 

79 from git import Repo 

80 

81 repo = Repo(directory, search_parent_directories=True) 

82 

83 return repo.is_dirty() 

84 

85 

86def git_commands_are_available(directory): 

87 """ 

88 True if "git" command executable is available, and ``directory`` is in a valid git repo. 

89 """ 

90 try: 

91 # pylint: disable=import-outside-toplevel 

92 # Third party libraries 

93 from git import InvalidGitRepositoryError, Repo 

94 except ImportError: 

95 return False 

96 

97 try: 

98 Repo(directory, search_parent_directories=True) 

99 except InvalidGitRepositoryError: 

100 return False 

101 

102 return True 

103 

104 

105def find_git_files( 

106 directory, 

107 exclude_directories=None, 

108 file_endings_include=None, 

109 file_endings_avoid=None, 

110): 

111 """ 

112 Find files that are checked in to git. 

113 

114 Arguments: 

115 directory (pathlib.Path): Search in this directory. 

116 exclude_directories (list(pathlib.Path)): Files in these directories will not be included. 

117 file_endings_include (str or tuple(str)). Only files with these endings will be included. 

118 file_endings_avoid (str or tuple(str)): String or tuple of strings. Files with these endings 

119 will not be included. 

120 

121 Returns: 

122 list(pathlib.Path): The files that are available in git. 

123 """ 

124 

125 # Import fails if "git" executable is not available, hence it can not be on top level. 

126 # This function should only be called if git is available. 

127 # pylint: disable=import-outside-toplevel 

128 # Third party libraries 

129 from git import Repo 

130 

131 exclude_directories = ( 

132 [] 

133 if exclude_directories is None 

134 else [exclude_directory.resolve() for exclude_directory in exclude_directories] 

135 ) 

136 

137 def list_paths(root_tree, path): 

138 for blob in root_tree.blobs: 

139 yield path / blob.name 

140 for tree in root_tree.trees: 

141 yield from list_paths(tree, path / tree.name) 

142 

143 repo = Repo(directory, search_parent_directories=True) 

144 repo_root = Path(repo.git_dir).parent.resolve() 

145 

146 for file_path in list_paths(repo.tree(), repo_root): 

147 if file_endings_include is not None and not file_path.name.endswith(file_endings_include): 

148 continue 

149 

150 if file_endings_avoid is not None and file_path.name.endswith(file_endings_avoid): 

151 continue 

152 

153 if file_is_in_directory(file_path, exclude_directories): 

154 continue 

155 

156 if file_is_in_directory(file_path, [directory]): 

157 yield file_path