Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored loading of checkout file, added support for xcscmblueprint file #141

Merged
merged 3 commits into from
Sep 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions BuildaKit/HDGitHubXCBotSyncer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ public class HDGitHubXCBotSyncer : Syncer {

public func syncPRsAndBranchesAndBots(repo repo: Repo, repoName: String, prs: [PullRequest], branches: [Branch], bots: [Bot], completion: () -> ()) {

let prsDescription = prs.map({ "\n\tPR \($0.number): \($0.title) [\($0.head.ref) -> \($0.base.ref)]" }) + ["\n"]
let branchesDescription = branches.map({ "\n\tBranch [\($0.name):\($0.commit.sha)]" }) + ["\n"]
let botsDescription = bots.map({ "\n\tBot \($0.name)" }) + ["\n"]
let prsDescription = prs.map({ " PR \($0.number): \($0.title) [\($0.head.ref) -> \($0.base.ref)]" }).joinWithSeparator("\n")
let branchesDescription = branches.map({ " Branch [\($0.name):\($0.commit.sha)]" }).joinWithSeparator("\n")
let botsDescription = bots.map({ " Bot \($0.name)" }).joinWithSeparator("\n")
Log.verbose("Resolving prs:\n\(prsDescription) \nand branches:\n\(branchesDescription)\nand bots:\n\(botsDescription)")

//create the changes necessary
Expand Down
134 changes: 11 additions & 123 deletions BuildaKit/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ import XcodeServerSDK

public class Project : JSONSerializable {

public enum AllowedCheckoutTypes: String {
case SSH = "SSH"
// case HTTPS - not yet supported, right now only SSH is supported
// (for bots reasons, will be built in when I have time)
// case SVN - not yet supported yet
}

public var url: NSURL {
didSet {
do { try self.refreshMetadata() } catch {}
Expand All @@ -35,68 +28,10 @@ public class Project : JSONSerializable {

public var availabilityState: AvailabilityCheckState

private(set) var workspaceMetadata: NSDictionary?
let forkOriginURL: String?

//convenience getters
public var projectName: String? { get { return self.pullValueForKey("IDESourceControlProjectName") }}
public var projectPath: String? { get { return self.pullValueForKey("IDESourceControlProjectPath") }}
public var projectWCCIdentifier: String? { get { return self.pullValueForKey("IDESourceControlProjectWCCIdentifier") }}
public var projectWCCName: String? {
get {
if let wccId = self.projectWCCIdentifier {
if let wcConfigs = self.workspaceMetadata?["IDESourceControlProjectWCConfigurations"] as? [NSDictionary] {
if let foundConfig = wcConfigs.filter({
if let loopWccId = $0.optionalStringForKey("IDESourceControlWCCIdentifierKey") {
return loopWccId == wccId
}
return false
}).first {
//so much effort for this little key...
return foundConfig.optionalStringForKey("IDESourceControlWCCName")
}
}
}
return nil
}
}
public var projectURL: NSURL? {
get {
if let urlString = self.pullValueForKey("IDESourceControlProjectURL") {

//if we have a fork, chose its URL, otherwise fallback to the loaded URL from the Checkout file
var finalUrlString = self.forkOriginURL ?? urlString
let type = self.checkoutType!
if type == .SSH {
if !finalUrlString.hasPrefix("git@") {
finalUrlString = "git@\(finalUrlString)"
}
}

return NSURL(string: finalUrlString)
}
return nil
}
}

public var checkoutType: AllowedCheckoutTypes? {
get {
if
let meta = self.workspaceMetadata,
let type = Project.parseCheckoutType(meta) {
return type
}
return nil
}
}

private func pullValueForKey(key: String) -> String? {
return self.workspaceMetadata?.optionalStringForKey(key)
}
private(set) public var workspaceMetadata: WorkspaceMetadata?

public init?(url: NSURL) {

self.forkOriginURL = nil
self.url = url
self.preferredTemplateId = nil
self.githubToken = nil
Expand All @@ -112,81 +47,33 @@ public class Project : JSONSerializable {
}
}

private init?(original: Project, forkOriginURL: String) {
private init(original: Project, forkOriginURL: String) throws {

self.forkOriginURL = forkOriginURL
self.url = original.url
self.preferredTemplateId = original.preferredTemplateId
self.githubToken = original.githubToken
self.availabilityState = original.availabilityState
self.publicSSHKeyUrl = original.publicSSHKeyUrl
self.privateSSHKeyUrl = original.privateSSHKeyUrl
self.sshPassphrase = original.sshPassphrase
do {
try self.refreshMetadata()
} catch {
Log.error(error)
return nil
}
self.workspaceMetadata = try original.workspaceMetadata?.duplicateWithForkURL(forkOriginURL)
}

public func duplicateForForkAtOriginURL(forkURL: String) -> Project? {

return Project(original: self, forkOriginURL: forkURL)
public func duplicateForForkAtOriginURL(forkURL: String) throws -> Project {
return try Project(original: self, forkOriginURL: forkURL)
}

public class func attemptToParseFromUrl(url: NSURL) throws -> NSDictionary {

let meta = try Project.loadWorkspaceMetadata(url)

//validate allowed remote url
if self.parseCheckoutType(meta) == nil {
//disallowed
let allowedString = [AllowedCheckoutTypes.SSH].map({ $0.rawValue }).joinWithSeparator(", ")
let error = Error.withInfo("Disallowed checkout type, the project must be checked out over one of the supported schemes: \(allowedString)")
throw error
}

return meta
}

private class func parseCheckoutType(metadata: NSDictionary) -> AllowedCheckoutTypes? {

if
let urlString = metadata.optionalStringForKey("IDESourceControlProjectURL"),
let url = NSURL(string: urlString)
{
let scheme = url.scheme
switch scheme {
case "github.com":
return AllowedCheckoutTypes.SSH
case "https":

if urlString.hasSuffix(".git") {
//HTTPS git
} else {
//SVN
}

Log.error("HTTPS or SVN not yet supported, please create an issue on GitHub if you want it added (czechboy0/Buildasaur)")
return nil
default:
return nil
}
} else {
return nil
}
public class func attemptToParseFromUrl(url: NSURL) throws -> WorkspaceMetadata {
return try Project.loadWorkspaceMetadata(url)
}

private func refreshMetadata() throws {

let meta = try Project.attemptToParseFromUrl(self.url)
self.workspaceMetadata = meta
}

public required init?(json: NSDictionary) {

self.forkOriginURL = nil
self.availabilityState = .Unchecked

if
Expand Down Expand Up @@ -224,19 +111,20 @@ public class Project : JSONSerializable {
self.publicSSHKeyUrl = nil
self.privateSSHKeyUrl = nil
self.sshPassphrase = nil
self.workspaceMetadata = nil
return nil
}
}

public init() {
self.forkOriginURL = nil
self.availabilityState = .Unchecked
self.url = NSURL()
self.preferredTemplateId = nil
self.githubToken = nil
self.publicSSHKeyUrl = nil
self.privateSSHKeyUrl = nil
self.sshPassphrase = nil
self.workspaceMetadata = nil
}

public func jsonify() -> NSDictionary {
Expand All @@ -260,14 +148,14 @@ public class Project : JSONSerializable {
return names
}

private class func loadWorkspaceMetadata(url: NSURL) throws -> NSDictionary {
private class func loadWorkspaceMetadata(url: NSURL) throws -> WorkspaceMetadata {

return try XcodeProjectParser.parseRepoMetadataFromProjectOrWorkspaceURL(url)
}

public func githubRepoName() -> String? {

if let projectUrl = self.projectURL {
if let projectUrl = self.workspaceMetadata?.projectURL {
let originalStringUrl = projectUrl.absoluteString
let stringUrl = originalStringUrl.lowercaseString

Expand Down
98 changes: 98 additions & 0 deletions BuildaKit/SourceControlFileParser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// SourceControlFileParser.swift
// Buildasaur
//
// Created by Honza Dvorsky on 29/09/2015.
// Copyright © 2015 Honza Dvorsky. All rights reserved.
//

import Foundation
import BuildaUtils

protocol SourceControlFileParser {

func supportedFileExtensions() -> [String]
func parseFileAtUrl(url: NSURL) throws -> WorkspaceMetadata
}

class CheckoutFileParser: SourceControlFileParser {

func supportedFileExtensions() -> [String] {
return ["xccheckout"]
}

func parseFileAtUrl(url: NSURL) throws -> WorkspaceMetadata {

//plist -> NSDictionary
guard let dictionary = NSDictionary(contentsOfURL: url) else { throw Error.withInfo("Failed to parse \(url)") }

//parse our required keys
let projectName = dictionary.optionalStringForKey("IDESourceControlProjectName")
let projectPath = dictionary.optionalStringForKey("IDESourceControlProjectPath")
let projectWCCIdentifier = dictionary.optionalStringForKey("IDESourceControlProjectWCCIdentifier")
let projectWCCName = { () -> String? in
if let wccId = projectWCCIdentifier {
if let wcConfigs = dictionary["IDESourceControlProjectWCConfigurations"] as? [NSDictionary] {
if let foundConfig = wcConfigs.filter({
if let loopWccId = $0.optionalStringForKey("IDESourceControlWCCIdentifierKey") {
return loopWccId == wccId
}
return false
}).first {
//so much effort for this little key...
return foundConfig.optionalStringForKey("IDESourceControlWCCName")
}
}
}
return nil
}()
let projectURLString = { dictionary.optionalStringForKey("IDESourceControlProjectURL") }()

return try WorkspaceMetadata(projectName: projectName, projectPath: projectPath, projectWCCIdentifier: projectWCCIdentifier, projectWCCName: projectWCCName, projectURLString: projectURLString)
}
}

class BlueprintFileParser: SourceControlFileParser {

func supportedFileExtensions() -> [String] {
return ["xcscmblueprint"]
}

func parseFileAtUrl(url: NSURL) throws -> WorkspaceMetadata {

//JSON -> NSDictionary
let data = try NSData(contentsOfURL: url, options: NSDataReadingOptions())
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
guard let dictionary = jsonObject as? NSDictionary else { throw Error.withInfo("Failed to parse \(url)") }

//parse our required keys
let projectName = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintNameKey")
let projectPath = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey")
let projectWCCIdentifier = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey")

var primaryRemoteRepositoryDictionary: NSDictionary?
if let wccId = projectWCCIdentifier {
if let wcConfigs = dictionary["DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey"] as? [NSDictionary] {
primaryRemoteRepositoryDictionary = wcConfigs.filter({
if let loopWccId = $0.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey") {
return loopWccId == wccId
}
return false
}).first
}
}

let projectURLString = primaryRemoteRepositoryDictionary?.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey")

var projectWCCName: String?
if
let copyPaths = dictionary["DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey"] as? [String: String],
let primaryRemoteRepoId = projectWCCIdentifier
{
projectWCCName = copyPaths[primaryRemoteRepoId]
}

return try WorkspaceMetadata(projectName: projectName, projectPath: projectPath, projectWCCIdentifier: projectWCCIdentifier, projectWCCName: projectWCCName, projectURLString: projectURLString)
}
}

9 changes: 5 additions & 4 deletions BuildaKit/SyncerBotManipulation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,17 @@ extension HDGitHubXCBotSyncer {

//to handle forks
let headOriginUrl = repo.repoUrlSSH
let localProjectOriginUrl = self.project.projectURL!.absoluteString
let localProjectOriginUrl = self.project.workspaceMetadata!.projectURL.absoluteString

let project: Project
if headOriginUrl != localProjectOriginUrl {

//we have a fork, duplicate the metadata with the fork's origin
if let source = self.project.duplicateForForkAtOriginURL(headOriginUrl) {
do {
let source = try self.project.duplicateForForkAtOriginURL(headOriginUrl)
project = source
} else {
self.notifyError(Error.withInfo("Couldn't create a Project for fork with origin at url \(headOriginUrl)"), context: "Creating a bot from a PR")
} catch {
self.notifyError(Error.withInfo("Couldn't create a Project for fork with origin at url \(headOriginUrl), error \(error)"), context: "Creating a bot from a PR")
completion()
return
}
Expand Down
Loading