Skip to content

Commit

Permalink
{CI} Add git hooks support (#30297)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
kairu-ms authored Nov 14, 2024
1 parent 5d0cf1a commit 73eb6dd
Show file tree
Hide file tree
Showing 10 changed files with 464 additions and 0 deletions.
47 changes: 47 additions & 0 deletions .githooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Git Hooks for Azure CLI Extension Development

## Setup

Please run the following command to enable the hooks.

```bash
azdev setup -c {azure_cli_repo_path} -r {azure_cli_extension_repo_path}

# if you install azdev which version is less than 0.1.84, you need to run the following command to enable the hooks
git config --local core.hooksPath .githooks
```

## Usage

Every time you git commit or git push, please make sure you have activated the python environment and completed the azdev setup.

If you want to skip the verification, you can add `--no-verify` to the git command.

## Note

### pre-commit

The pre-commit hook (`pre-commit.ps1`) performs the following checks:

1. Verifies that azdev is active in your current environment
2. Runs `azdev scan` on all staged files to detect potential secrets
3. If any secrets are detected, the commit will be blocked
- You can use `azdev mask` to remove secrets before committing
- Alternatively, use `git commit --no-verify` to bypass the check

### pre-push

The pre-push hooks (`pre-push.sh` for bash and `pre-push.ps1` for PowerShell) perform several quality checks:

1. Verifies that azdev is active in your current environment
2. Confirms azure-cli is installed in editable mode
3. Checks if your branch needs rebasing against upstream/dev
- If rebasing is needed, displays instructions and provides a 5-second window to cancel
4. Runs the following quality checks on changed files:
- `azdev lint`: Checks for linting issues
- `azdev style`: Verifies code style compliance
- `azdev test`: Runs tests for modified code
5. If any check fails, the push will be blocked
- Use `git push --no-verify` to bypass these checks (not recommended)

The hooks support both Windows (PowerShell) and Unix-like systems (Bash), automatically using the appropriate script for your environment.
39 changes: 39 additions & 0 deletions .githooks/azdev_active.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Check if in the python environment
$pythonPath = (Get-Command python -ErrorAction SilentlyContinue).Path
Write-Host "PYTHON_PATH: $pythonPath"

if (-not $pythonPath) {
Write-Host "Error: Python not found in PATH" -ForegroundColor Red
exit 1
}

$pythonEnvFolder = Split-Path -Parent (Split-Path -Parent $pythonPath)
$pythonActiveFile = Join-Path $pythonEnvFolder "Scripts\activate.ps1"

if (-not (Test-Path $pythonActiveFile)) {
Write-Host "Python active file does not exist: $pythonActiveFile" -ForegroundColor Red
Write-Host "Error: Please activate the python environment first." -ForegroundColor Red
exit 1
}

# Construct the full path to the .azdev\env_config directory
$azdevEnvConfigFolder = Join-Path $env:USERPROFILE ".azdev\env_config"
Write-Host "AZDEV_ENV_CONFIG_FOLDER: $azdevEnvConfigFolder"

# Check if the directory exists
if (-not (Test-Path $azdevEnvConfigFolder)) {
Write-Host "AZDEV_ENV_CONFIG_FOLDER does not exist: $azdevEnvConfigFolder" -ForegroundColor Red
Write-Host "Error: azdev environment is not completed, please run 'azdev setup' first." -ForegroundColor Red
exit 1
}

$configFile = Join-Path $azdevEnvConfigFolder ($pythonEnvFolder.Substring(2) + "\config")
if (-not (Test-Path $configFile)) {
Write-Host "CONFIG_FILE does not exist: $configFile" -ForegroundColor Red
Write-Host "Error: azdev environment is not completed, please run 'azdev setup' first." -ForegroundColor Red
exit 1
}

Write-Host "CONFIG_FILE: $configFile"

exit 0
41 changes: 41 additions & 0 deletions .githooks/azdev_active.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Check if in the python environment
PYTHON_FILE=$(which python)
printf "PYTHON_PATH: %s\n" "$PYTHON_FILE"

if [ -z "$PYTHON_FILE" ]; then
printf "\033[0;31mError: Python not found in PATH\033[0m\n"
exit 1
fi

PYTHON_ENV_FOLDER=$(dirname "$PYTHON_FILE")
PYTHON_ACTIVE_FILE="$PYTHON_ENV_FOLDER/activate"

if [ ! -f "$PYTHON_ACTIVE_FILE" ]; then
printf "Python active file does not exist: %s\n" "$PYTHON_ACTIVE_FILE"
printf "\033[0;31mError: Please activate the python environment first.\033[0m\n"
exit 1
fi

# Construct the full path to the .azdev/env_config directory
AZDEV_ENV_CONFIG_FOLDER="$HOME/.azdev/env_config"
printf "AZDEV_ENV_CONFIG_FOLDER: %s\n" "$AZDEV_ENV_CONFIG_FOLDER"

# Check if the directory exists
if [ ! -d "$AZDEV_ENV_CONFIG_FOLDER" ]; then
printf "AZDEV_ENV_CONFIG_FOLDER does not exist: %s\n" "$AZDEV_ENV_CONFIG_FOLDER"
printf "\033[0;31mError: azdev environment is not completed, please run 'azdev setup' first.\033[0m\n"
exit 1
fi

PYTHON_ENV_FOLDER=$(dirname "$PYTHON_ENV_FOLDER")

CONFIG_FILE="$AZDEV_ENV_CONFIG_FOLDER${PYTHON_ENV_FOLDER}/config"
if [ ! -f "$CONFIG_FILE" ]; then
printf "CONFIG_FILE does not exist: %s\n" "$CONFIG_FILE"
printf "\033[0;31mError: azdev environment is not completed, please run 'azdev setup' first.\033[0m\n"
exit 1
fi

printf "CONFIG_FILE: %s\n" "$CONFIG_FILE"
13 changes: 13 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env sh
":" //; 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
":" //; exit # Skip rest on Unix

@echo off
powershell -NoProfile -Command "if (Get-Command powershell -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"
if %errorlevel% equ 0 (
powershell -ExecutionPolicy Bypass -File .githooks\pre-commit.ps1
) else (
echo Error: PowerShell is not available. Please install PowerShell.
exit /b 1
)
exit /b %errorlevel%
43 changes: 43 additions & 0 deletions .githooks/pre-commit.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env pwsh
Write-Host "Running pre-commit hook in powershell..." -ForegroundColor Green

# run azdev_active script
$scriptPath = Join-Path $PSScriptRoot "azdev_active.ps1"
. $scriptPath
if ($LASTEXITCODE -ne 0) {
exit 1
}

# Run command azdev scan
Write-Host "Running azdev scan..." -ForegroundColor Green

# Check if we have a previous commit to compare against
if (git rev-parse --verify HEAD 2>$null) {
Write-Host "Using HEAD as the previous commit"
$against = "HEAD"
}
else {
# Initial commit: diff against an empty tree object
Write-Host "Using empty tree object as the previous commit"
$against = $(git hash-object -t tree /dev/null)
}

$hasSecrets = 0
$files = $(git diff --cached --name-only --diff-filter=AM $against)

foreach ($file in $files) {
# Check if the file contains secrets
$detected = $(azdev scan -f $file | ConvertFrom-Json).secrets_detected
if ($detected -eq "True") {
Write-Host "Detected secrets from $file. You can run 'azdev mask' to remove secrets before commit." -ForegroundColor Red
$hasSecrets = 1
}
}

if ($hasSecrets -eq 1) {
Write-Host "Secret detected. If you want to skip that, run add '--no-verify' in the end of 'git commit' command." -ForegroundColor Red
exit 1
}

Write-Host "Pre-commit hook passed." -ForegroundColor Green
exit 0
38 changes: 38 additions & 0 deletions .githooks/pre-commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash
printf "\033[0;32mRunning pre-commit hook in bash ...\033[0m\n"

# run azdev_active script
SCRIPT_PATH="$(dirname "$0")/azdev_active.sh"
. "$SCRIPT_PATH"
if [ $? -ne 0 ]; then
exit 1
fi

# Run command azdev scan
printf "\033[0;32mRunning azdev scan...\033[0m\n"

if git rev-parse --verify HEAD >/dev/null 2>&1
then
printf "Using HEAD as the previous commit\n"
against=HEAD
else
printf "Using empty tree object as the previous commit\n"
against=$(git hash-object -t tree /dev/null)
fi
has_secrets=0
for FILE in `git diff --cached --name-only --diff-filter=AM $against` ; do
# Check if the file contains secrets
detected=$(azdev scan -f "$FILE" | python -c "import sys, json; print(json.load(sys.stdin)['secrets_detected'])")
if [ "$detected" = "True" ]; then
printf "\033[0;31mDetected secrets from %s, You can run 'azdev mask' to remove secrets before commit.\033[0m\n" "$FILE"
has_secrets=1
fi
done

if [ $has_secrets -eq 1 ]; then
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"
exit 1
fi

printf "\033[0;32mPre-commit hook passed.\033[0m\n"
exit 0
13 changes: 13 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env sh
":" //; 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
":" //; exit # Skip rest on Unix

@echo off
powershell -NoProfile -Command "if (Get-Command powershell -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"
if %errorlevel% equ 0 (
powershell -ExecutionPolicy Bypass -File .githooks\pre-push.ps1
) else (
echo Error: PowerShell is not available. Please install PowerShell.
exit /b 1
)
exit /b %errorlevel%
109 changes: 109 additions & 0 deletions .githooks/pre-push.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
Write-Host "Running pre-push hook in powershell..." -ForegroundColor Green

# run azdev_active script and capture its output
$scriptPath = Join-Path $PSScriptRoot "azdev_active.ps1"
. $scriptPath
if ($LASTEXITCODE -ne 0) {
exit 1
}

# Check if azure-cli is installed in editable mode
$pipShowOutput = pip show azure-cli 2>&1
$editableLocation = if ($pipShowOutput) {
$match = $pipShowOutput | Select-String "Editable project location: (.+)"
if ($match) {
$match.Matches.Groups[1].Value
}
}
if ($editableLocation) {
# get the parent of parent directory of the editable location
$AZURE_CLI_FOLDER = Split-Path -Parent (Split-Path -Parent $editableLocation)
} else {
Write-Host "Error: azure-cli is not installed in editable mode. Please install it in editable mode using `azdev setup`." -ForegroundColor Red
exit 1
}

# Get extension repo paths and join them with spaces
$Extensions = (azdev extension repo list -o tsv) -join ' '

# Fetch upstream/dev branch
Write-Host "Fetching upstream/dev branch..." -ForegroundColor Green
git fetch upstream dev
if ($LASTEXITCODE -ne 0) {
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
exit 1
}

# Check if current branch needs rebasing
$mergeBase = git merge-base HEAD upstream/dev
$upstreamHead = git rev-parse upstream/dev
if ($mergeBase -ne $upstreamHead) {
Write-Host ""
Write-Host "Your branch is not up to date with upstream/dev. Please run the following commands to rebase and setup:" -ForegroundColor Yellow
Write-Host "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" -ForegroundColor Yellow
Write-Host "git rebase upstream/dev" -ForegroundColor Yellow
if ($Extensions) {
Write-Host "azdev setup -c $AZURE_CLI_FOLDER -r $Extensions" -ForegroundColor Yellow
} else {
Write-Host "azdev setup -c $AZURE_CLI_FOLDER" -ForegroundColor Yellow
}
Write-Host "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" -ForegroundColor Yellow
Write-Host ""
Write-Host "You have 5 seconds to stop the push (Ctrl+C)..." -ForegroundColor Yellow
for ($i = 5; $i -gt 0; $i--) {
Write-Host "`rTime remaining: $i seconds..." -NoNewline -ForegroundColor Yellow
Start-Sleep -Seconds 1
}
Write-Host "`rContinuing without rebase..."
}

# get the current branch name
$currentBranch = git branch --show-current

# Run command azdev lint
Write-Host "Running azdev lint..." -ForegroundColor Green
azdev linter --min-severity medium --repo ./ --src $currentBranch --tgt $mergeBase
if ($LASTEXITCODE -ne 0) {
Write-Host "Error: azdev lint check failed." -ForegroundColor Red
exit 1
}

# Run command azdev style
Write-Host "Running azdev style..." -ForegroundColor Green
azdev style --repo ./ --src $currentBranch --tgt $mergeBase
if ($LASTEXITCODE -ne 0) {
$error_msg = azdev style --repo ./ --src $currentBranch --tgt $mergeBase 2>&1
if ($error_msg -like "*No modules*") {
Write-Host "Pre-push hook passed." -ForegroundColor Green
exit 0
}
Write-Host "Error: azdev style check failed." -ForegroundColor Red
exit 1
}

# Run command azdev test
Write-Host "Running azdev test..." -ForegroundColor Green
azdev test --repo ./ --src $currentBranch --tgt $mergeBase --discover --no-exitfirst --xml-path test_results.xml 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Host "Error: azdev test check failed." -ForegroundColor Red
exit 1
} else {
# remove test_results.xml file
Remove-Item test_results.xml
}

Write-Host "Pre-push hook passed." -ForegroundColor Green

if ($mergeBase -ne $upstreamHead) {
Write-Host ""
Write-Host "Your branch is not up to date with upstream/dev. Please run the following commands to rebase code and setup:" -ForegroundColor Yellow
Write-Host "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" -ForegroundColor Yellow
Write-Host "git rebase upstream/dev" -ForegroundColor Yellow
if ($Extensions) {
Write-Host "azdev setup -c $AZURE_CLI_FOLDER -r $Extensions" -ForegroundColor Yellow
} else {
Write-Host "azdev setup -c $AZURE_CLI_FOLDER" -ForegroundColor Yellow
}
Write-Host "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" -ForegroundColor Yellow
}
exit 0
Loading

0 comments on commit 73eb6dd

Please sign in to comment.