@aaronshaf/ger
Version:
Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS
119 lines (100 loc) • 3.63 kB
text/typescript
/**
* Utility functions for parsing Gerrit URLs and extracting change numbers
*/
/**
* Extracts a change number from various Gerrit URL formats
*
* Supported formats:
* - https://gerrit.example.com/c/project-name/+/123456
* - https://gerrit.example.com/c/project-name/+/123456/
* - https://gerrit.example.com/c/project-name/+/123456/1
* - https://gerrit.example.com/#/c/project-name/+/123456/
* - 123456 (plain change number - returned as-is)
*
* @param input - The input string (URL or change number)
* @returns The extracted change number as a string, or the original input if not a URL
*/
export const extractChangeNumber = (input: string): string => {
const trimmed = input.trim()
// If it's already just a number or change ID (like "123456" or "Iabcd1234..."), return as-is
if (!trimmed.startsWith('http://') && !trimmed.startsWith('https://')) {
return trimmed
}
// Parse URL and extract change number
try {
const url = new URL(trimmed)
// Match different Gerrit URL patterns
// Pattern: /c/project-name/+/123456 or /#/c/project-name/+/123456
const patterns = [
/\/c\/[^/]+\/\+\/(\d+)/, // /c/project/+/123456
/#\/c\/[^/]+\/\+\/(\d+)/, // /#/c/project/+/123456
/\/c\/\+\/(\d+)/, // /c/+/123456 (simplified format)
/#\/c\/\+\/(\d+)/, // /#/c/+/123456 (simplified format)
]
const fullPath = url.pathname + url.hash
for (const pattern of patterns) {
const match = fullPath.match(pattern)
if (match?.[1]) {
return match[1]
}
}
// If no pattern matches, return the original input
return trimmed
} catch {
// If URL parsing fails, return the original input
return trimmed
}
}
/**
* Normalizes a Gerrit host URL by adding https:// if no protocol is provided
* and removing trailing slashes
*
* @param host - The host URL to normalize (e.g., "gerrit.example.com" or "https://gerrit.example.com")
* @returns The normalized URL with protocol and without trailing slash
*
* @example
* normalizeGerritHost("gerrit.example.com") // returns "https://gerrit.example.com"
* normalizeGerritHost("gerrit.example.com:8080") // returns "https://gerrit.example.com:8080"
* normalizeGerritHost("http://gerrit.example.com") // returns "http://gerrit.example.com"
* normalizeGerritHost("https://gerrit.example.com/") // returns "https://gerrit.example.com"
*/
export const normalizeGerritHost = (host: string): string => {
let normalized = host.trim()
// Add https:// if no protocol provided
if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {
normalized = `https://${normalized}`
}
// Remove trailing slash
normalized = normalized.replace(/\/$/, '')
return normalized
}
/**
* Validates if a string is a valid Gerrit change identifier
*
* @param changeId - The change ID to validate
* @returns true if it looks like a valid change ID
*/
export const isValidChangeId = (changeId: string): boolean => {
const trimmed = changeId.trim()
if (trimmed.length === 0) {
return false
}
// Numeric change IDs (most common)
if (/^\d+$/.test(trimmed)) {
return parseInt(trimmed, 10) > 0
}
// Change-Id format (starts with 'I' followed by exactly 40 hex characters)
if (/^I[a-f0-9]{40}$/.test(trimmed)) {
return true
}
// Reject strings with whitespace
if (/\s/.test(trimmed)) {
return false
}
// Reject negative numbers or other invalid formats
if (trimmed.startsWith('-')) {
return false
}
// Topic branches or other identifiers (at least 1 character, no whitespace)
return trimmed.length > 0
}