Coverage for tsfpga/git_utils.py: 96%

49 statements  

« 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# -------------------------------------------------------------------------------------------------- 

8 

9import os 

10from pathlib import Path 

11 

12from tsfpga.system_utils import file_is_in_directory 

13 

14 

15def get_git_commit(directory): 

16 """ 

17 Get a string describing the current git commit. 

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

19 

20 Arguments: 

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

22 

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)" 

29 

30 return git_commit 

31 

32 

33def get_git_sha(directory): 

34 """ 

35 Get a short git SHA. 

36 

37 Arguments: 

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

39 

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 

47 

48 if "GIT_COMMIT" in os.environ: 

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

50 

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 

55 

56 repo = Repo(directory, search_parent_directories=True) 

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

58 

59 return git_sha 

60 

61 

62def git_local_changes_present(directory): 

63 """ 

64 Check if the git repo has local changes. 

65 

66 Arguments: 

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

68 

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 

76 

77 repo = Repo(directory, search_parent_directories=True) 

78 

79 return repo.is_dirty() 

80 

81 

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 

91 

92 try: 

93 Repo(directory, search_parent_directories=True) 

94 except InvalidGitRepositoryError: 

95 return False 

96 

97 return True 

98 

99 

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. 

108 

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. 

115 

116 Returns: 

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

118 """ 

119 

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 

124 

125 exclude_directories = ( 

126 [] 

127 if exclude_directories is None 

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

129 ) 

130 

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) 

136 

137 repo = Repo(directory, search_parent_directories=True) 

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

139 

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 

143 

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

145 continue 

146 

147 if file_is_in_directory(file_path, exclude_directories): 

148 continue 

149 

150 if file_is_in_directory(file_path, [directory]): 

151 yield file_path