1""" 2tags for common git attributes 3""" 4import os 5import subprocess 6from typing import Dict 7from typing import MutableMapping 8from typing import Optional 9from typing import Tuple 10 11import six 12 13from ddtrace.internal import compat 14from ddtrace.internal.logger import get_logger 15 16 17if six.PY2: 18 GitNotFoundError = OSError 19else: 20 GitNotFoundError = FileNotFoundError 21 22# Git Branch 23BRANCH = "git.branch" 24 25# Git Commit SHA 26COMMIT_SHA = "git.commit.sha" 27 28# Git Repository URL 29REPOSITORY_URL = "git.repository_url" 30 31# Git Tag 32TAG = "git.tag" 33 34# Git Commit Author Name 35COMMIT_AUTHOR_NAME = "git.commit.author.name" 36 37# Git Commit Author Email 38COMMIT_AUTHOR_EMAIL = "git.commit.author.email" 39 40# Git Commit Author Date (UTC) 41COMMIT_AUTHOR_DATE = "git.commit.author.date" 42 43# Git Commit Committer Name 44COMMIT_COMMITTER_NAME = "git.commit.committer.name" 45 46# Git Commit Committer Email 47COMMIT_COMMITTER_EMAIL = "git.commit.committer.email" 48 49# Git Commit Committer Date (UTC) 50COMMIT_COMMITTER_DATE = "git.commit.committer.date" 51 52# Git Commit Message 53COMMIT_MESSAGE = "git.commit.message" 54 55log = get_logger(__name__) 56 57 58def _git_subprocess_cmd(cmd, cwd=None): 59 # type: (str, Optional[str]) -> str 60 """Helper for invoking the git CLI binary.""" 61 git_cmd = cmd.split(" ") 62 git_cmd.insert(0, "git") 63 process = subprocess.Popen(git_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) 64 stdout, stderr = process.communicate() 65 if process.returncode == 0: 66 return compat.ensure_text(stdout).strip() 67 raise ValueError(stderr) 68 69 70def extract_user_info(cwd=None): 71 # type: (Optional[str]) -> Dict[str, Tuple[str, str, str]] 72 """Extract commit author info from the git repository in the current directory or one specified by ``cwd``.""" 73 # Note: `git show -s --format... --date...` is supported since git 2.1.4 onwards 74 stdout = _git_subprocess_cmd("show -s --format=%an,%ae,%ad,%cn,%ce,%cd --date=format:%Y-%m-%dT%H:%M:%S%z", cwd=cwd) 75 author_name, author_email, author_date, committer_name, committer_email, committer_date = stdout.split(",") 76 return { 77 "author": (author_name, author_email, author_date), 78 "committer": (committer_name, committer_email, committer_date), 79 } 80 81 82def extract_repository_url(cwd=None): 83 # type: (Optional[str]) -> str 84 """Extract the repository url from the git repository in the current directory or one specified by ``cwd``.""" 85 # Note: `git show ls-remote --get-url` is supported since git 2.6.7 onwards 86 repository_url = _git_subprocess_cmd("ls-remote --get-url", cwd=cwd) 87 return repository_url 88 89 90def extract_commit_message(cwd=None): 91 # type: (Optional[str]) -> str 92 """Extract git commit message from the git repository in the current directory or one specified by ``cwd``.""" 93 # Note: `git show -s --format... --date...` is supported since git 2.1.4 onwards 94 commit_message = _git_subprocess_cmd("show -s --format=%s", cwd=cwd) 95 return commit_message 96 97 98def extract_workspace_path(cwd=None): 99 # type: (Optional[str]) -> str 100 """Extract the root directory path from the git repository in the current directory or one specified by ``cwd``.""" 101 workspace_path = _git_subprocess_cmd("rev-parse --show-toplevel", cwd=cwd) 102 return workspace_path 103 104 105def extract_branch(cwd=None): 106 # type: (Optional[str]) -> str 107 """Extract git branch from the git repository in the current directory or one specified by ``cwd``.""" 108 branch = _git_subprocess_cmd("rev-parse --abbrev-ref HEAD", cwd=cwd) 109 return branch 110 111 112def extract_commit_sha(cwd=None): 113 # type: (Optional[str]) -> str 114 """Extract git commit SHA from the git repository in the current directory or one specified by ``cwd``.""" 115 commit_sha = _git_subprocess_cmd("rev-parse HEAD", cwd=cwd) 116 return commit_sha 117 118 119def extract_git_metadata(cwd=None): 120 # type: (Optional[str]) -> Dict[str, Optional[str]] 121 """Extract git commit metadata.""" 122 tags = {} # type: Dict[str, Optional[str]] 123 try: 124 tags[REPOSITORY_URL] = extract_repository_url(cwd=cwd) 125 tags[COMMIT_MESSAGE] = extract_commit_message(cwd=cwd) 126 users = extract_user_info(cwd=cwd) 127 tags[COMMIT_AUTHOR_NAME] = users["author"][0] 128 tags[COMMIT_AUTHOR_EMAIL] = users["author"][1] 129 tags[COMMIT_AUTHOR_DATE] = users["author"][2] 130 tags[COMMIT_COMMITTER_NAME] = users["committer"][0] 131 tags[COMMIT_COMMITTER_EMAIL] = users["committer"][1] 132 tags[COMMIT_COMMITTER_DATE] = users["committer"][2] 133 tags[BRANCH] = extract_branch(cwd=cwd) 134 tags[COMMIT_SHA] = extract_commit_sha(cwd=cwd) 135 except GitNotFoundError: 136 log.error("Git executable not found, cannot extract git metadata.") 137 except ValueError: 138 log.error("Error extracting git metadata, received non-zero return code.", exc_info=True) 139 140 return tags 141 142 143def extract_user_git_metadata(env=None): 144 # type: (Optional[MutableMapping[str, str]]) -> Dict[str, Optional[str]] 145 """Extract git commit metadata from user-provided env vars.""" 146 env = os.environ if env is None else env 147 148 tags = {} 149 tags[REPOSITORY_URL] = env.get("DD_GIT_REPOSITORY_URL") 150 tags[COMMIT_SHA] = env.get("DD_GIT_COMMIT_SHA") 151 tags[BRANCH] = env.get("DD_GIT_BRANCH") 152 tags[TAG] = env.get("DD_GIT_TAG") 153 tags[COMMIT_MESSAGE] = env.get("DD_GIT_COMMIT_MESSAGE") 154 tags[COMMIT_AUTHOR_DATE] = env.get("DD_GIT_COMMIT_AUTHOR_DATE") 155 tags[COMMIT_AUTHOR_EMAIL] = env.get("DD_GIT_COMMIT_AUTHOR_EMAIL") 156 tags[COMMIT_AUTHOR_NAME] = env.get("DD_GIT_COMMIT_AUTHOR_NAME") 157 tags[COMMIT_COMMITTER_DATE] = env.get("DD_GIT_COMMIT_COMMITTER_DATE") 158 tags[COMMIT_COMMITTER_EMAIL] = env.get("DD_GIT_COMMIT_COMMITTER_EMAIL") 159 tags[COMMIT_COMMITTER_NAME] = env.get("DD_GIT_COMMIT_COMMITTER_NAME") 160 161 return tags 162