Skip to content

Commit 73eb6dd

Browse files
authored
{CI} Add git hooks support (#30297)
* add githooks * fix src and tgt * Add rebase check * add rebase check * Add rebase upstream/dev warning. * Using merge base as target * test * print the warning again * +x for hooks * Replace echo with printf * fix printf * remove line for test * Improve the code for tests * Add docs for .githooks * use medium min-servity for azdev linter.
1 parent 5d0cf1a commit 73eb6dd

File tree

10 files changed

+464
-0
lines changed

10 files changed

+464
-0
lines changed

.githooks/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Git Hooks for Azure CLI Extension Development
2+
3+
## Setup
4+
5+
Please run the following command to enable the hooks.
6+
7+
```bash
8+
azdev setup -c {azure_cli_repo_path} -r {azure_cli_extension_repo_path}
9+
10+
# if you install azdev which version is less than 0.1.84, you need to run the following command to enable the hooks
11+
git config --local core.hooksPath .githooks
12+
```
13+
14+
## Usage
15+
16+
Every time you git commit or git push, please make sure you have activated the python environment and completed the azdev setup.
17+
18+
If you want to skip the verification, you can add `--no-verify` to the git command.
19+
20+
## Note
21+
22+
### pre-commit
23+
24+
The pre-commit hook (`pre-commit.ps1`) performs the following checks:
25+
26+
1. Verifies that azdev is active in your current environment
27+
2. Runs `azdev scan` on all staged files to detect potential secrets
28+
3. If any secrets are detected, the commit will be blocked
29+
- You can use `azdev mask` to remove secrets before committing
30+
- Alternatively, use `git commit --no-verify` to bypass the check
31+
32+
### pre-push
33+
34+
The pre-push hooks (`pre-push.sh` for bash and `pre-push.ps1` for PowerShell) perform several quality checks:
35+
36+
1. Verifies that azdev is active in your current environment
37+
2. Confirms azure-cli is installed in editable mode
38+
3. Checks if your branch needs rebasing against upstream/dev
39+
- If rebasing is needed, displays instructions and provides a 5-second window to cancel
40+
4. Runs the following quality checks on changed files:
41+
- `azdev lint`: Checks for linting issues
42+
- `azdev style`: Verifies code style compliance
43+
- `azdev test`: Runs tests for modified code
44+
5. If any check fails, the push will be blocked
45+
- Use `git push --no-verify` to bypass these checks (not recommended)
46+
47+
The hooks support both Windows (PowerShell) and Unix-like systems (Bash), automatically using the appropriate script for your environment.

.githooks/azdev_active.ps1

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Check if in the python environment
2+
$pythonPath = (Get-Command python -ErrorAction SilentlyContinue).Path
3+
Write-Host "PYTHON_PATH: $pythonPath"
4+
5+
if (-not $pythonPath) {
6+
Write-Host "Error: Python not found in PATH" -ForegroundColor Red
7+
exit 1
8+
}
9+
10+
$pythonEnvFolder = Split-Path -Parent (Split-Path -Parent $pythonPath)
11+
$pythonActiveFile = Join-Path $pythonEnvFolder "Scripts\activate.ps1"
12+
13+
if (-not (Test-Path $pythonActiveFile)) {
14+
Write-Host "Python active file does not exist: $pythonActiveFile" -ForegroundColor Red
15+
Write-Host "Error: Please activate the python environment first." -ForegroundColor Red
16+
exit 1
17+
}
18+
19+
# Construct the full path to the .azdev\env_config directory
20+
$azdevEnvConfigFolder = Join-Path $env:USERPROFILE ".azdev\env_config"
21+
Write-Host "AZDEV_ENV_CONFIG_FOLDER: $azdevEnvConfigFolder"
22+
23+
# Check if the directory exists
24+
if (-not (Test-Path $azdevEnvConfigFolder)) {
25+
Write-Host "AZDEV_ENV_CONFIG_FOLDER does not exist: $azdevEnvConfigFolder" -ForegroundColor Red
26+
Write-Host "Error: azdev environment is not completed, please run 'azdev setup' first." -ForegroundColor Red
27+
exit 1
28+
}
29+
30+
$configFile = Join-Path $azdevEnvConfigFolder ($pythonEnvFolder.Substring(2) + "\config")
31+
if (-not (Test-Path $configFile)) {
32+
Write-Host "CONFIG_FILE does not exist: $configFile" -ForegroundColor Red
33+
Write-Host "Error: azdev environment is not completed, please run 'azdev setup' first." -ForegroundColor Red
34+
exit 1
35+
}
36+
37+
Write-Host "CONFIG_FILE: $configFile"
38+
39+
exit 0

.githooks/azdev_active.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
3+
# Check if in the python environment
4+
PYTHON_FILE=$(which python)
5+
printf "PYTHON_PATH: %s\n" "$PYTHON_FILE"
6+
7+
if [ -z "$PYTHON_FILE" ]; then
8+
printf "\033[0;31mError: Python not found in PATH\033[0m\n"
9+
exit 1
10+
fi
11+
12+
PYTHON_ENV_FOLDER=$(dirname "$PYTHON_FILE")
13+
PYTHON_ACTIVE_FILE="$PYTHON_ENV_FOLDER/activate"
14+
15+
if [ ! -f "$PYTHON_ACTIVE_FILE" ]; then
16+
printf "Python active file does not exist: %s\n" "$PYTHON_ACTIVE_FILE"
17+
printf "\033[0;31mError: Please activate the python environment first.\033[0m\n"
18+
exit 1
19+
fi
20+
21+
# Construct the full path to the .azdev/env_config directory
22+
AZDEV_ENV_CONFIG_FOLDER="$HOME/.azdev/env_config"
23+
printf "AZDEV_ENV_CONFIG_FOLDER: %s\n" "$AZDEV_ENV_CONFIG_FOLDER"
24+
25+
# Check if the directory exists
26+
if [ ! -d "$AZDEV_ENV_CONFIG_FOLDER" ]; then
27+
printf "AZDEV_ENV_CONFIG_FOLDER does not exist: %s\n" "$AZDEV_ENV_CONFIG_FOLDER"
28+
printf "\033[0;31mError: azdev environment is not completed, please run 'azdev setup' first.\033[0m\n"
29+
exit 1
30+
fi
31+
32+
PYTHON_ENV_FOLDER=$(dirname "$PYTHON_ENV_FOLDER")
33+
34+
CONFIG_FILE="$AZDEV_ENV_CONFIG_FOLDER${PYTHON_ENV_FOLDER}/config"
35+
if [ ! -f "$CONFIG_FILE" ]; then
36+
printf "CONFIG_FILE does not exist: %s\n" "$CONFIG_FILE"
37+
printf "\033[0;31mError: azdev environment is not completed, please run 'azdev setup' first.\033[0m\n"
38+
exit 1
39+
fi
40+
41+
printf "CONFIG_FILE: %s\n" "$CONFIG_FILE"

.githooks/pre-commit

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env sh
2+
":" //; if command -v pwsh >/dev/null 2>&1; then pwsh -ExecutionPolicy Bypass -File .githooks/pre-commit.ps1; else sh .githooks/pre-commit.sh; fi; exit $? # Try PowerShell Core first, then sh on Unix
3+
":" //; exit # Skip rest on Unix
4+
5+
@echo off
6+
powershell -NoProfile -Command "if (Get-Command powershell -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"
7+
if %errorlevel% equ 0 (
8+
powershell -ExecutionPolicy Bypass -File .githooks\pre-commit.ps1
9+
) else (
10+
echo Error: PowerShell is not available. Please install PowerShell.
11+
exit /b 1
12+
)
13+
exit /b %errorlevel%

.githooks/pre-commit.ps1

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env pwsh
2+
Write-Host "Running pre-commit hook in powershell..." -ForegroundColor Green
3+
4+
# run azdev_active script
5+
$scriptPath = Join-Path $PSScriptRoot "azdev_active.ps1"
6+
. $scriptPath
7+
if ($LASTEXITCODE -ne 0) {
8+
exit 1
9+
}
10+
11+
# Run command azdev scan
12+
Write-Host "Running azdev scan..." -ForegroundColor Green
13+
14+
# Check if we have a previous commit to compare against
15+
if (git rev-parse --verify HEAD 2>$null) {
16+
Write-Host "Using HEAD as the previous commit"
17+
$against = "HEAD"
18+
}
19+
else {
20+
# Initial commit: diff against an empty tree object
21+
Write-Host "Using empty tree object as the previous commit"
22+
$against = $(git hash-object -t tree /dev/null)
23+
}
24+
25+
$hasSecrets = 0
26+
$files = $(git diff --cached --name-only --diff-filter=AM $against)
27+
28+
foreach ($file in $files) {
29+
# Check if the file contains secrets
30+
$detected = $(azdev scan -f $file | ConvertFrom-Json).secrets_detected
31+
if ($detected -eq "True") {
32+
Write-Host "Detected secrets from $file. You can run 'azdev mask' to remove secrets before commit." -ForegroundColor Red
33+
$hasSecrets = 1
34+
}
35+
}
36+
37+
if ($hasSecrets -eq 1) {
38+
Write-Host "Secret detected. If you want to skip that, run add '--no-verify' in the end of 'git commit' command." -ForegroundColor Red
39+
exit 1
40+
}
41+
42+
Write-Host "Pre-commit hook passed." -ForegroundColor Green
43+
exit 0

.githooks/pre-commit.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
printf "\033[0;32mRunning pre-commit hook in bash ...\033[0m\n"
3+
4+
# run azdev_active script
5+
SCRIPT_PATH="$(dirname "$0")/azdev_active.sh"
6+
. "$SCRIPT_PATH"
7+
if [ $? -ne 0 ]; then
8+
exit 1
9+
fi
10+
11+
# Run command azdev scan
12+
printf "\033[0;32mRunning azdev scan...\033[0m\n"
13+
14+
if git rev-parse --verify HEAD >/dev/null 2>&1
15+
then
16+
printf "Using HEAD as the previous commit\n"
17+
against=HEAD
18+
else
19+
printf "Using empty tree object as the previous commit\n"
20+
against=$(git hash-object -t tree /dev/null)
21+
fi
22+
has_secrets=0
23+
for FILE in `git diff --cached --name-only --diff-filter=AM $against` ; do
24+
# Check if the file contains secrets
25+
detected=$(azdev scan -f "$FILE" | python -c "import sys, json; print(json.load(sys.stdin)['secrets_detected'])")
26+
if [ "$detected" = "True" ]; then
27+
printf "\033[0;31mDetected secrets from %s, You can run 'azdev mask' to remove secrets before commit.\033[0m\n" "$FILE"
28+
has_secrets=1
29+
fi
30+
done
31+
32+
if [ $has_secrets -eq 1 ]; then
33+
printf "\033[0;31mSecret detected. If you want to skip that, run add '--no-verify' in the end of 'git commit' command.\033[0m\n"
34+
exit 1
35+
fi
36+
37+
printf "\033[0;32mPre-commit hook passed.\033[0m\n"
38+
exit 0

.githooks/pre-push

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env sh
2+
":" //; if command -v pwsh >/dev/null 2>&1; then pwsh -ExecutionPolicy Bypass -File .githooks/pre-push.ps1; else sh .githooks/pre-push.sh; fi; exit $? # Try PowerShell Core first, then sh on Unix
3+
":" //; exit # Skip rest on Unix
4+
5+
@echo off
6+
powershell -NoProfile -Command "if (Get-Command powershell -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"
7+
if %errorlevel% equ 0 (
8+
powershell -ExecutionPolicy Bypass -File .githooks\pre-push.ps1
9+
) else (
10+
echo Error: PowerShell is not available. Please install PowerShell.
11+
exit /b 1
12+
)
13+
exit /b %errorlevel%

.githooks/pre-push.ps1

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
Write-Host "Running pre-push hook in powershell..." -ForegroundColor Green
2+
3+
# run azdev_active script and capture its output
4+
$scriptPath = Join-Path $PSScriptRoot "azdev_active.ps1"
5+
. $scriptPath
6+
if ($LASTEXITCODE -ne 0) {
7+
exit 1
8+
}
9+
10+
# Check if azure-cli is installed in editable mode
11+
$pipShowOutput = pip show azure-cli 2>&1
12+
$editableLocation = if ($pipShowOutput) {
13+
$match = $pipShowOutput | Select-String "Editable project location: (.+)"
14+
if ($match) {
15+
$match.Matches.Groups[1].Value
16+
}
17+
}
18+
if ($editableLocation) {
19+
# get the parent of parent directory of the editable location
20+
$AZURE_CLI_FOLDER = Split-Path -Parent (Split-Path -Parent $editableLocation)
21+
} else {
22+
Write-Host "Error: azure-cli is not installed in editable mode. Please install it in editable mode using `azdev setup`." -ForegroundColor Red
23+
exit 1
24+
}
25+
26+
# Get extension repo paths and join them with spaces
27+
$Extensions = (azdev extension repo list -o tsv) -join ' '
28+
29+
# Fetch upstream/dev branch
30+
Write-Host "Fetching upstream/dev branch..." -ForegroundColor Green
31+
git fetch upstream dev
32+
if ($LASTEXITCODE -ne 0) {
33+
Write-Host "Error: Failed to fetch upstream/dev branch. Please run 'git remote add upstream https://github.com/Azure/azure-cli.git' first." -ForegroundColor Red
34+
exit 1
35+
}
36+
37+
# Check if current branch needs rebasing
38+
$mergeBase = git merge-base HEAD upstream/dev
39+
$upstreamHead = git rev-parse upstream/dev
40+
if ($mergeBase -ne $upstreamHead) {
41+
Write-Host ""
42+
Write-Host "Your branch is not up to date with upstream/dev. Please run the following commands to rebase and setup:" -ForegroundColor Yellow
43+
Write-Host "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" -ForegroundColor Yellow
44+
Write-Host "git rebase upstream/dev" -ForegroundColor Yellow
45+
if ($Extensions) {
46+
Write-Host "azdev setup -c $AZURE_CLI_FOLDER -r $Extensions" -ForegroundColor Yellow
47+
} else {
48+
Write-Host "azdev setup -c $AZURE_CLI_FOLDER" -ForegroundColor Yellow
49+
}
50+
Write-Host "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" -ForegroundColor Yellow
51+
Write-Host ""
52+
Write-Host "You have 5 seconds to stop the push (Ctrl+C)..." -ForegroundColor Yellow
53+
for ($i = 5; $i -gt 0; $i--) {
54+
Write-Host "`rTime remaining: $i seconds..." -NoNewline -ForegroundColor Yellow
55+
Start-Sleep -Seconds 1
56+
}
57+
Write-Host "`rContinuing without rebase..."
58+
}
59+
60+
# get the current branch name
61+
$currentBranch = git branch --show-current
62+
63+
# Run command azdev lint
64+
Write-Host "Running azdev lint..." -ForegroundColor Green
65+
azdev linter --min-severity medium --repo ./ --src $currentBranch --tgt $mergeBase
66+
if ($LASTEXITCODE -ne 0) {
67+
Write-Host "Error: azdev lint check failed." -ForegroundColor Red
68+
exit 1
69+
}
70+
71+
# Run command azdev style
72+
Write-Host "Running azdev style..." -ForegroundColor Green
73+
azdev style --repo ./ --src $currentBranch --tgt $mergeBase
74+
if ($LASTEXITCODE -ne 0) {
75+
$error_msg = azdev style --repo ./ --src $currentBranch --tgt $mergeBase 2>&1
76+
if ($error_msg -like "*No modules*") {
77+
Write-Host "Pre-push hook passed." -ForegroundColor Green
78+
exit 0
79+
}
80+
Write-Host "Error: azdev style check failed." -ForegroundColor Red
81+
exit 1
82+
}
83+
84+
# Run command azdev test
85+
Write-Host "Running azdev test..." -ForegroundColor Green
86+
azdev test --repo ./ --src $currentBranch --tgt $mergeBase --discover --no-exitfirst --xml-path test_results.xml 2>$null
87+
if ($LASTEXITCODE -ne 0) {
88+
Write-Host "Error: azdev test check failed." -ForegroundColor Red
89+
exit 1
90+
} else {
91+
# remove test_results.xml file
92+
Remove-Item test_results.xml
93+
}
94+
95+
Write-Host "Pre-push hook passed." -ForegroundColor Green
96+
97+
if ($mergeBase -ne $upstreamHead) {
98+
Write-Host ""
99+
Write-Host "Your branch is not up to date with upstream/dev. Please run the following commands to rebase code and setup:" -ForegroundColor Yellow
100+
Write-Host "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" -ForegroundColor Yellow
101+
Write-Host "git rebase upstream/dev" -ForegroundColor Yellow
102+
if ($Extensions) {
103+
Write-Host "azdev setup -c $AZURE_CLI_FOLDER -r $Extensions" -ForegroundColor Yellow
104+
} else {
105+
Write-Host "azdev setup -c $AZURE_CLI_FOLDER" -ForegroundColor Yellow
106+
}
107+
Write-Host "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" -ForegroundColor Yellow
108+
}
109+
exit 0

0 commit comments

Comments
 (0)