Sources/XCMetricsPlugins/GitPlugin.swift (49 lines of code) (raw):
// Copyright (c) 2021 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import Foundation
import XCMetricsClient
import XCMetricsUtils
public struct GitPlugin {
public enum GitData {
// The current branch name
case branch
// The status of the repo
case isDirty
// The most recent commit hash in short format (6)
case latestSHA
// The branch's creator's email, redacted if necessary
case userEmail(redacted: Bool)
}
private let gitDirectoryPath: String
private let gitData: [GitData]
private let shell: ShellOutFunction
/// Initializes a `GitPlugin` using a configurable location for the git directory
/// as well as the git data to attach to the associated build metadata.
/// - Parameter gitDirectoryPath: The location of the git directory to use - usually Xcode's `${SRCROOT}` environment variable can be used (this variable is not available in the default environment variables for XCMetrics, and would need to be passed in).
/// - Parameter gitData: The data to be retrieved from git (eg: the build's current branch). Available data points can be found and added to the `GitData` enumeration.
/// - Parameter shell: Will default to `shellGetStdout`
public init(gitDirectoryPath: String, gitData: [GitData] = [.branch, .latestSHA, .isDirty, .userEmail(redacted: true)], shell: @escaping ShellOutFunction = shellGetStdout) {
self.gitDirectoryPath = gitDirectoryPath
self.gitData = gitData
self.shell = shell
}
public func create() -> XCMetricsPlugin {
return XCMetricsPlugin(name: "Git", body: { _ -> [String : String] in
guard !gitData.isEmpty else { return [:] }
return gitData.reduce(into: [String: String]()) {
dictionary, target in
switch target {
case .branch:
if let branch = try? shell("git", ["-C", gitDirectoryPath, "rev-parse", "--abbrev-ref", "HEAD"], nil, nil) {
dictionary["git_branch"] = branch
}
case .isDirty:
let hasChanges = try? shell("git", ["-C", gitDirectoryPath, "status", "--short"], nil, nil)
// account for nil and empty string output
dictionary["git_is_dirty"] = hasChanges.isNilOrEmpty ? "false" : "true"
case .latestSHA:
if let latestSHA = try? shell("git", ["-C", gitDirectoryPath, "rev-parse", "--short=6", "--verify", "HEAD"], nil, nil) {
dictionary["git_commit_sha"] = latestSHA
}
case .userEmail(let redacted):
if let userEmail = try? shell("git", ["-C", gitDirectoryPath, "config", "--get", "user.email"], nil, nil) {
dictionary["git_user_email"] = redacted ? userEmail.md5() : userEmail
}
}
}
})
}
}
fileprivate extension Optional where Wrapped: Collection {
var isNilOrEmpty: Bool {
return self?.isEmpty ?? true
}
}