Include commit metadata in cmux --version output
This commit is contained in:
parent
915c01f9d0
commit
f7ef0707c2
2 changed files with 175 additions and 20 deletions
110
CLI/cmux.swift
110
CLI/cmux.swift
|
|
@ -4988,39 +4988,63 @@ struct CMUXCLI {
|
|||
|
||||
private func versionSummary() -> String {
|
||||
let info = resolvedVersionInfo()
|
||||
let commit = info["CMUXCommit"].flatMap { normalizedCommitHash($0) }
|
||||
let baseSummary: String
|
||||
if let version = info["CFBundleShortVersionString"], let build = info["CFBundleVersion"] {
|
||||
return "cmux \(version) (\(build))"
|
||||
baseSummary = "cmux \(version) (\(build))"
|
||||
} else if let version = info["CFBundleShortVersionString"] {
|
||||
baseSummary = "cmux \(version)"
|
||||
} else if let build = info["CFBundleVersion"] {
|
||||
baseSummary = "cmux build \(build)"
|
||||
} else {
|
||||
baseSummary = "cmux version unknown"
|
||||
}
|
||||
if let version = info["CFBundleShortVersionString"] {
|
||||
return "cmux \(version)"
|
||||
}
|
||||
if let build = info["CFBundleVersion"] {
|
||||
return "cmux build \(build)"
|
||||
}
|
||||
return "cmux version unknown"
|
||||
guard let commit else { return baseSummary }
|
||||
return "\(baseSummary) [\(commit)]"
|
||||
}
|
||||
|
||||
private func resolvedVersionInfo() -> [String: String] {
|
||||
var info: [String: String] = [:]
|
||||
if let main = versionInfo(from: Bundle.main.infoDictionary) {
|
||||
return main
|
||||
info.merge(main, uniquingKeysWith: { current, _ in current })
|
||||
}
|
||||
|
||||
for plistURL in candidateInfoPlistURLs() {
|
||||
guard let data = try? Data(contentsOf: plistURL),
|
||||
let raw = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil),
|
||||
let dictionary = raw as? [String: Any],
|
||||
let parsed = versionInfo(from: dictionary)
|
||||
else {
|
||||
continue
|
||||
let needsPlistFallback =
|
||||
info["CFBundleShortVersionString"] == nil ||
|
||||
info["CFBundleVersion"] == nil ||
|
||||
info["CMUXCommit"] == nil
|
||||
if needsPlistFallback {
|
||||
for plistURL in candidateInfoPlistURLs() {
|
||||
guard let data = try? Data(contentsOf: plistURL),
|
||||
let raw = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil),
|
||||
let dictionary = raw as? [String: Any],
|
||||
let parsed = versionInfo(from: dictionary)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
info.merge(parsed, uniquingKeysWith: { current, _ in current })
|
||||
if info["CFBundleShortVersionString"] != nil,
|
||||
info["CFBundleVersion"] != nil,
|
||||
info["CMUXCommit"] != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
if let fromProject = versionInfoFromProjectFile() {
|
||||
return fromProject
|
||||
let needsProjectFallback =
|
||||
info["CFBundleShortVersionString"] == nil ||
|
||||
info["CFBundleVersion"] == nil ||
|
||||
info["CMUXCommit"] == nil
|
||||
if needsProjectFallback, let fromProject = versionInfoFromProjectFile() {
|
||||
info.merge(fromProject, uniquingKeysWith: { current, _ in current })
|
||||
}
|
||||
|
||||
return [:]
|
||||
if info["CMUXCommit"] == nil,
|
||||
let commit = normalizedCommitHash(ProcessInfo.processInfo.environment["CMUX_COMMIT"]) {
|
||||
info["CMUXCommit"] = commit
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
private func versionInfo(from dictionary: [String: Any]?) -> [String: String]? {
|
||||
|
|
@ -5039,6 +5063,10 @@ struct CMUXCLI {
|
|||
info["CFBundleVersion"] = trimmed
|
||||
}
|
||||
}
|
||||
if let commit = dictionary["CMUXCommit"] as? String,
|
||||
let normalizedCommit = normalizedCommitHash(commit) {
|
||||
info["CMUXCommit"] = normalizedCommit
|
||||
}
|
||||
return info.isEmpty ? nil : info
|
||||
}
|
||||
|
||||
|
|
@ -5064,6 +5092,9 @@ struct CMUXCLI {
|
|||
if let build = firstProjectSetting("CURRENT_PROJECT_VERSION", in: contents) {
|
||||
info["CFBundleVersion"] = build
|
||||
}
|
||||
if let commit = gitCommitHash(at: current) {
|
||||
info["CMUXCommit"] = commit
|
||||
}
|
||||
if !info.isEmpty {
|
||||
return info
|
||||
}
|
||||
|
|
@ -5100,6 +5131,45 @@ struct CMUXCLI {
|
|||
return value
|
||||
}
|
||||
|
||||
private func gitCommitHash(at directory: URL) -> String? {
|
||||
let process = Process()
|
||||
let stdout = Pipe()
|
||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
|
||||
process.arguments = ["git", "-C", directory.path, "rev-parse", "--short=9", "HEAD"]
|
||||
process.standardOutput = stdout
|
||||
process.standardError = Pipe()
|
||||
|
||||
do {
|
||||
try process.run()
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
process.waitUntilExit()
|
||||
guard process.terminationStatus == 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let data = stdout.fileHandleForReading.readDataToEndOfFile()
|
||||
guard let output = String(data: data, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
return normalizedCommitHash(output)
|
||||
}
|
||||
|
||||
private func normalizedCommitHash(_ value: String?) -> String? {
|
||||
guard let value else { return nil }
|
||||
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty, !trimmed.contains("$(") else {
|
||||
return nil
|
||||
}
|
||||
let normalized = trimmed.lowercased()
|
||||
let allowed = CharacterSet(charactersIn: "0123456789abcdef")
|
||||
guard normalized.unicodeScalars.allSatisfy({ allowed.contains($0) }) else {
|
||||
return nil
|
||||
}
|
||||
return String(normalized.prefix(12))
|
||||
}
|
||||
|
||||
private func candidateInfoPlistURLs() -> [URL] {
|
||||
guard let executable = currentExecutablePath(), !executable.isEmpty else {
|
||||
return []
|
||||
|
|
|
|||
85
tests/test_cli_version_commit_metadata.py
Normal file
85
tests/test_cli_version_commit_metadata.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Regression test: CLI version output wiring keeps commit metadata support."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_repo_root() -> Path:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return Path(result.stdout.strip())
|
||||
return Path.cwd()
|
||||
|
||||
|
||||
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
|
||||
if needle not in content:
|
||||
failures.append(message)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
repo_root = get_repo_root()
|
||||
cli_path = repo_root / "CLI" / "cmux.swift"
|
||||
if not cli_path.exists():
|
||||
print(f"FAIL: missing expected file: {cli_path}")
|
||||
return 1
|
||||
|
||||
content = cli_path.read_text(encoding="utf-8")
|
||||
failures: list[str] = []
|
||||
|
||||
require(
|
||||
content,
|
||||
'let commit = info["CMUXCommit"].flatMap { normalizedCommitHash($0) }',
|
||||
"versionSummary no longer reads CMUXCommit metadata",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
content,
|
||||
'return "\\(baseSummary) [\\(commit)]"',
|
||||
"versionSummary no longer appends commit metadata",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
content,
|
||||
'if let commit = dictionary["CMUXCommit"] as? String,',
|
||||
"Info.plist parsing no longer reads CMUXCommit",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
content,
|
||||
"if let commit = gitCommitHash(at: current) {",
|
||||
"Project fallback no longer probes git commit hash",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
content,
|
||||
'["git", "-C", directory.path, "rev-parse", "--short=9", "HEAD"]',
|
||||
"Git commit probe command changed unexpectedly",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
content,
|
||||
'normalizedCommitHash(ProcessInfo.processInfo.environment["CMUX_COMMIT"])',
|
||||
"Environment commit fallback (CMUX_COMMIT) is missing",
|
||||
failures,
|
||||
)
|
||||
|
||||
if failures:
|
||||
print("FAIL: CLI version commit metadata regression(s) detected")
|
||||
for failure in failures:
|
||||
print(f"- {failure}")
|
||||
return 1
|
||||
|
||||
print("PASS: CLI version commit metadata wiring is intact")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue