Skip to content

Commit c28a138

Browse files
authored
feat: enhance branch detection with CI support and robust fallbacks (#104)
- Add comprehensive CI environment variable support for GitLab, GitHub, and Bitbucket - Implement priority system: CLI args → CI variables → git properties → defaults - Add robust fallback chain for branch detection in detached HEAD scenarios - Replace hard failures with sensible defaults (socket-default-repo, socket-default-branch) - Fix GitLab CI branch detection issue where branch was showing as None - Support additional commit SHA sources (CI_COMMIT_SHA, BITBUCKET_COMMIT) - Enhance default branch detection logic for all CI platforms - Improve error handling and logging throughout git interface Resolves GitLab pipeline branch detection failures and ensures CLI never fails due to missing repository or branch information.
1 parent e901515 commit c28a138

File tree

4 files changed

+127
-15
lines changed

4 files changed

+127
-15
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.1.26"
9+
version = "2.1.27"
1010
requires-python = ">= 3.10"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.1.26'
2+
__version__ = '2.1.27'

socketsecurity/core/git_interface.py

Lines changed: 119 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,24 @@ def __init__(self, path: str):
1616
assert self.repo
1717
self.head = self.repo.head
1818

19-
# Use GITHUB_SHA if available, otherwise fall back to current HEAD commit
19+
# Use CI environment SHA if available, otherwise fall back to current HEAD commit
2020
github_sha = os.getenv('GITHUB_SHA')
21-
if github_sha:
21+
gitlab_sha = os.getenv('CI_COMMIT_SHA')
22+
bitbucket_sha = os.getenv('BITBUCKET_COMMIT')
23+
ci_sha = github_sha or gitlab_sha or bitbucket_sha
24+
25+
if ci_sha:
2226
try:
23-
self.commit = self.repo.commit(github_sha)
24-
log.debug(f"Using commit from GITHUB_SHA: {github_sha}")
27+
self.commit = self.repo.commit(ci_sha)
28+
if github_sha:
29+
env_source = "GITHUB_SHA"
30+
elif gitlab_sha:
31+
env_source = "CI_COMMIT_SHA"
32+
else:
33+
env_source = "BITBUCKET_COMMIT"
34+
log.debug(f"Using commit from {env_source}: {ci_sha}")
2535
except Exception as error:
26-
log.debug(f"Failed to get commit from GITHUB_SHA: {error}")
36+
log.debug(f"Failed to get commit from CI environment: {error}")
2737
# Use the actual current HEAD commit, not the head reference's commit
2838
self.commit = self.repo.commit('HEAD')
2939
log.debug(f"Using current HEAD commit: {self.commit.hexsha}")
@@ -36,13 +46,84 @@ def __init__(self, path: str):
3646
log.debug(f"Commit author: {self.commit.author.name} <{self.commit.author.email}>")
3747
log.debug(f"Commit committer: {self.commit.committer.name} <{self.commit.committer.email}>")
3848

39-
self.repo_name = self.repo.remotes.origin.url.split('.git')[0].split('/')[-1]
49+
# Extract repository name from git remote, with fallback to default
4050
try:
41-
self.branch = self.head.reference
42-
urllib.parse.unquote(str(self.branch))
51+
remote_url = self.repo.remotes.origin.url
52+
self.repo_name = remote_url.split('.git')[0].split('/')[-1]
53+
log.debug(f"Repository name detected from git remote: {self.repo_name}")
4354
except Exception as error:
44-
self.branch = None
45-
log.debug(error)
55+
log.debug(f"Failed to get repository name from git remote: {error}")
56+
self.repo_name = "socket-default-repo"
57+
log.debug(f"Using default repository name: {self.repo_name}")
58+
59+
# Branch detection with priority: CI Variables -> Git Properties -> Default
60+
# Note: CLI arguments are handled in socketcli.py and take highest priority
61+
62+
# First, try CI environment variables (most accurate in CI environments)
63+
ci_branch = None
64+
65+
# GitLab CI variables
66+
gitlab_branch = os.getenv('CI_COMMIT_BRANCH') or os.getenv('CI_MERGE_REQUEST_SOURCE_BRANCH_NAME')
67+
68+
# GitHub Actions variables
69+
github_ref = os.getenv('GITHUB_REF') # e.g., 'refs/heads/main'
70+
github_branch = None
71+
if github_ref and github_ref.startswith('refs/heads/'):
72+
github_branch = github_ref.replace('refs/heads/', '')
73+
74+
# Bitbucket Pipelines variables
75+
bitbucket_branch = os.getenv('BITBUCKET_BRANCH')
76+
77+
# Select CI branch with priority: GitLab -> GitHub -> Bitbucket
78+
ci_branch = gitlab_branch or github_branch or bitbucket_branch
79+
80+
if ci_branch:
81+
self.branch = ci_branch
82+
if gitlab_branch:
83+
env_source = "GitLab CI"
84+
elif github_branch:
85+
env_source = "GitHub Actions"
86+
else:
87+
env_source = "Bitbucket Pipelines"
88+
log.debug(f"Branch detected from {env_source}: {self.branch}")
89+
else:
90+
# Try to get branch name from git properties
91+
try:
92+
self.branch = self.head.reference
93+
urllib.parse.unquote(str(self.branch))
94+
log.debug(f"Branch detected from git reference: {self.branch}")
95+
except Exception as error:
96+
log.debug(f"Failed to get branch from git reference: {error}")
97+
98+
# Fallback: try to detect branch from git commands (works in detached HEAD)
99+
git_detected_branch = None
100+
try:
101+
# Try git name-rev first (most reliable for detached HEAD)
102+
result = self.repo.git.name_rev('--name-only', 'HEAD')
103+
if result and result != 'undefined':
104+
# Clean up the result (remove any prefixes like 'remotes/origin/')
105+
git_detected_branch = result.split('/')[-1]
106+
log.debug(f"Branch detected from git name-rev: {git_detected_branch}")
107+
except Exception as git_error:
108+
log.debug(f"git name-rev failed: {git_error}")
109+
110+
if not git_detected_branch:
111+
try:
112+
# Fallback: try git describe --all --exact-match
113+
result = self.repo.git.describe('--all', '--exact-match', 'HEAD')
114+
if result and result.startswith('heads/'):
115+
git_detected_branch = result.replace('heads/', '')
116+
log.debug(f"Branch detected from git describe: {git_detected_branch}")
117+
except Exception as git_error:
118+
log.debug(f"git describe failed: {git_error}")
119+
120+
if git_detected_branch:
121+
self.branch = git_detected_branch
122+
log.debug(f"Branch detected from git commands: {self.branch}")
123+
else:
124+
# Final fallback: use default branch name
125+
self.branch = "socket-default-branch"
126+
log.debug(f"Using default branch name: {self.branch}")
46127
self.author = self.commit.author
47128
self.commit_sha = self.commit.binsha
48129
self.commit_message = self.commit.message
@@ -72,9 +153,14 @@ def _is_commit_and_branch_default(self) -> bool:
72153
log.debug("Commit is not on default branch")
73154
return False
74155

75-
# Check if we're processing the default branch
156+
# Check if we're processing the default branch via CI environment variables
76157
github_ref = os.getenv('GITHUB_REF') # e.g., 'refs/heads/main' or 'refs/pull/123/merge'
158+
gitlab_branch = os.getenv('CI_COMMIT_BRANCH')
159+
gitlab_mr_branch = os.getenv('CI_MERGE_REQUEST_SOURCE_BRANCH_NAME')
160+
gitlab_default_branch = os.getenv('CI_DEFAULT_BRANCH', '')
161+
bitbucket_branch = os.getenv('BITBUCKET_BRANCH')
77162

163+
# Handle GitHub Actions
78164
if github_ref:
79165
log.debug(f"GitHub ref: {github_ref}")
80166

@@ -94,6 +180,28 @@ def _is_commit_and_branch_default(self) -> bool:
94180
# Handle tags or other refs - not default branch
95181
log.debug(f"Non-branch ref: {github_ref}, not default branch")
96182
return False
183+
184+
# Handle GitLab CI
185+
elif gitlab_branch or gitlab_mr_branch:
186+
# If this is a merge request, use the source branch
187+
current_branch = gitlab_mr_branch or gitlab_branch
188+
default_branch_name = gitlab_default_branch or self.get_default_branch_name()
189+
190+
# For merge requests, they're typically not considered "default branch"
191+
if gitlab_mr_branch:
192+
log.debug(f"Processing GitLab MR from branch: {gitlab_mr_branch}, not default branch")
193+
return False
194+
195+
is_default = current_branch == default_branch_name
196+
log.debug(f"GitLab branch: {current_branch}, Default: {default_branch_name}, Is default: {is_default}")
197+
return is_default
198+
199+
# Handle Bitbucket Pipelines
200+
elif bitbucket_branch:
201+
default_branch_name = self.get_default_branch_name()
202+
is_default = bitbucket_branch == default_branch_name
203+
log.debug(f"Bitbucket branch: {bitbucket_branch}, Default: {default_branch_name}, Is default: {is_default}")
204+
return is_default
97205
else:
98206
# Not in GitHub Actions, use local development logic
99207
# For local development, we consider it "default branch" if:

socketsecurity/socketcli.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,12 @@ def main_code():
136136
raise Exception(f"Unable to find path {config.target_path}")
137137

138138
if not config.repo:
139-
log.info("Repo name needs to be set")
140-
sys.exit(2)
139+
config.repo = "socket-default-repo"
140+
log.debug(f"Using default repository name: {config.repo}")
141+
142+
if not config.branch:
143+
config.branch = "socket-default-branch"
144+
log.debug(f"Using default branch name: {config.branch}")
141145

142146
scm = None
143147
if config.scm == "github":

0 commit comments

Comments
 (0)