diff --git a/BuildaGitServer/Base/Authentication.swift b/BuildaGitServer/Base/Authentication.swift new file mode 100644 index 0000000..9a465a3 --- /dev/null +++ b/BuildaGitServer/Base/Authentication.swift @@ -0,0 +1,64 @@ +// +// Authentication.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/26/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation +import BuildaUtils + +public struct ProjectAuthenticator { + + public enum AuthType: String { + case PersonalToken + case OAuthToken + } + + public let service: GitService + public let username: String + public let type: AuthType + public let secret: String + + public init(service: GitService, username: String, type: AuthType, secret: String) { + self.service = service + self.username = username + self.type = type + self.secret = secret + } +} + +public protocol KeychainStringSerializable { + static func fromString(value: String) throws -> Self + func toString() -> String +} + +extension ProjectAuthenticator: KeychainStringSerializable { + + public static func fromString(value: String) throws -> ProjectAuthenticator { + + let comps = value.componentsSeparatedByString(":") + guard comps.count >= 4 else { throw Error.withInfo("Corrupted keychain string") } + guard let service = GitService(rawValue: comps[0]) else { + throw Error.withInfo("Unsupported service: \(comps[0])") + } + guard let type = ProjectAuthenticator.AuthType(rawValue: comps[2]) else { + throw Error.withInfo("Unsupported auth type: \(comps[2])") + } + //join the rest back in case we have ":" in the token + let remaining = comps.dropFirst(3).joinWithSeparator(":") + let auth = ProjectAuthenticator(service: service, username: comps[1], type: type, secret: remaining) + return auth + } + + public func toString() -> String { + + return [ + self.service.rawValue, + self.username, + self.type.rawValue, + self.secret + ].joinWithSeparator(":") + } +} diff --git a/BuildaGitServer/Base/BaseTypes.swift b/BuildaGitServer/Base/BaseTypes.swift new file mode 100644 index 0000000..9807eaf --- /dev/null +++ b/BuildaGitServer/Base/BaseTypes.swift @@ -0,0 +1,114 @@ +// +// BaseTypes.swift +// Buildasaur +// +// Created by Honza Dvorsky on 10/16/15. +// Copyright © 2015 Honza Dvorsky. All rights reserved. +// + +import Foundation +import ReactiveCocoa + +public protocol BuildStatusCreator { + func createStatusFromState(state: BuildState, description: String?, targetUrl: String?) -> StatusType +} + +public protocol SourceServerType: BuildStatusCreator { + + func getBranchesOfRepo(repo: String, completion: (branches: [BranchType]?, error: ErrorType?) -> ()) + func getOpenPullRequests(repo: String, completion: (prs: [PullRequestType]?, error: ErrorType?) -> ()) + func getPullRequest(pullRequestNumber: Int, repo: String, completion: (pr: PullRequestType?, error: ErrorType?) -> ()) + func getRepo(repo: String, completion: (repo: RepoType?, error: ErrorType?) -> ()) + func getStatusOfCommit(commit: String, repo: String, completion: (status: StatusType?, error: ErrorType?) -> ()) + func postStatusOfCommit(commit: String, status: StatusType, repo: String, completion: (status: StatusType?, error: ErrorType?) -> ()) + func postCommentOnIssue(comment: String, issueNumber: Int, repo: String, completion: (comment: CommentType?, error: ErrorType?) -> ()) + func getCommentsOfIssue(issueNumber: Int, repo: String, completion: (comments: [CommentType]?, error: ErrorType?) -> ()) + + func authChangedSignal() -> Signal +} + +public class SourceServerFactory { + + public init() { } + + public func createServer(service: GitService, auth: ProjectAuthenticator?) -> SourceServerType { + + if let auth = auth { + precondition(service == auth.service) + } + + return GitServerFactory.server(service, auth: auth) + } +} + +public struct RepoPermissions { + public let read: Bool + public let write: Bool + public init(read: Bool, write: Bool) { + self.read = read + self.write = write + } +} + +public protocol RateLimitType { + + var report: String { get } +} + +public protocol RepoType { + + var permissions: RepoPermissions { get } + var originUrlSSH: String { get } + var latestRateLimitInfo: RateLimitType? { get } +} + +public protocol BranchType { + + var name: String { get } + var commitSHA: String { get } +} + +public protocol IssueType { + + var number: Int { get } +} + +public protocol PullRequestType: IssueType { + + var headName: String { get } + var headCommitSHA: String { get } + var headRepo: RepoType { get } + + var baseName: String { get } + + var title: String { get } +} + +public enum BuildState { + case NoState + case Pending + case Success + case Error + case Failure +} + +public protocol StatusType { + + var state: BuildState { get } + var description: String? { get } + var targetUrl: String? { get } +} + +extension StatusType { + + public func isEqual(rhs: StatusType) -> Bool { + let lhs = self + return lhs.state == rhs.state && lhs.description == rhs.description + } +} + +public protocol CommentType { + + var body: String { get } +} + diff --git a/BuildaGitServer/Base/GitServerFactory.swift b/BuildaGitServer/Base/GitServerFactory.swift new file mode 100644 index 0000000..a2f1186 --- /dev/null +++ b/BuildaGitServer/Base/GitServerFactory.swift @@ -0,0 +1,32 @@ +// +// GitServerFactory.swift +// Buildasaur +// +// Created by Honza Dvorsky on 13/12/2014. +// Copyright (c) 2014 Honza Dvorsky. All rights reserved. +// + +import Foundation +import BuildaUtils + +class GitServerFactory { + + class func server(service: GitService, auth: ProjectAuthenticator?, http: HTTP? = nil) -> SourceServerType { + + let server: SourceServerType + + switch service { + case .GitHub: + let baseURL = "https://api.github.com" + let endpoints = GitHubEndpoints(baseURL: baseURL, auth: auth) + server = GitHubServer(endpoints: endpoints, http: http) + case .BitBucket: + let baseURL = "https://api.bitbucket.org" + let endpoints = BitBucketEndpoints(baseURL: baseURL, auth: auth) + server = BitBucketServer(endpoints: endpoints, http: http) + } + + return server + } + +} diff --git a/BuildaGitServer/GitHubServerExtensions.swift b/BuildaGitServer/Base/SourceServerExtensions.swift similarity index 87% rename from BuildaGitServer/GitHubServerExtensions.swift rename to BuildaGitServer/Base/SourceServerExtensions.swift index 466654f..5593c35 100644 --- a/BuildaGitServer/GitHubServerExtensions.swift +++ b/BuildaGitServer/Base/SourceServerExtensions.swift @@ -1,5 +1,5 @@ // -// GitHubServerExtensions.swift +// SourceServerExtensions.swift // Buildasaur // // Created by Honza Dvorsky on 14/12/2014. @@ -10,12 +10,12 @@ import Foundation import BuildaUtils //functions to make working with github easier - utility functions -public extension GitHubServer { +extension SourceServerType { /** * Get the latest status of a pull request. */ - public func getStatusOfPullRequest(pullRequestNumber: Int, repo: String, completion: (status: Status?, error: NSError?) -> ()) { + func getStatusOfPullRequest(pullRequestNumber: Int, repo: String, completion: (status: StatusType?, error: ErrorType?) -> ()) { self.getPullRequest(pullRequestNumber, repo: repo) { (pr, error) -> () in @@ -26,7 +26,7 @@ public extension GitHubServer { if let pr = pr { //fetched PR, now take its head's sha - that's the commit we care about. - let sha = pr.head.sha + let sha = pr.headName self.getStatusOfCommit(sha, repo: repo, completion: completion) } else { completion(status: nil, error: Error.withInfo("PR is nil and error is nil")) @@ -35,7 +35,7 @@ public extension GitHubServer { } //TODO: support paging through all the comments. currently we only fetch the last ~30 comments. - public func findMatchingCommentInIssue(commentsToMatch: [String], issue: Int, repo: String, completion: (foundComments: [Comment]?, error: NSError?) -> ()) { + public func findMatchingCommentInIssue(commentsToMatch: [String], issue: Int, repo: String, completion: (foundComments: [CommentType]?, error: ErrorType?) -> ()) { self.getCommentsOfIssue(issue, repo: repo) { (comments, error) -> () in @@ -45,7 +45,7 @@ public extension GitHubServer { } if let comments = comments { - let filtered = comments.filter { (comment: Comment) -> Bool in + let filtered = comments.filter { (comment: CommentType) -> Bool in let filteredSearch = commentsToMatch.filter { (searchString: String) -> Bool in diff --git a/BuildaGitServer/BitBucket/BitBucketComment.swift b/BuildaGitServer/BitBucket/BitBucketComment.swift new file mode 100644 index 0000000..88f0e1c --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketComment.swift @@ -0,0 +1,25 @@ +// +// BitBucketComment.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class BitBucketComment: BitBucketEntity, CommentType { + + let body: String + + required init(json: NSDictionary) { + + self.body = json + .optionalDictionaryForKey("content")? + .stringForKey("raw") ?? json.stringForKey("content") + + super.init(json: json) + } + + +} diff --git a/BuildaGitServer/BitBucket/BitBucketEndpoints.swift b/BuildaGitServer/BitBucket/BitBucketEndpoints.swift new file mode 100644 index 0000000..94b2c7d --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketEndpoints.swift @@ -0,0 +1,154 @@ +// +// BitBucketEndpoints.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation +import BuildaUtils +import ReactiveCocoa + +class BitBucketEndpoints { + + enum Endpoint { + case Repos + case PullRequests + case PullRequestComments + case CommitStatuses + } + + private let baseURL: String + internal let auth = MutableProperty(nil) + + init(baseURL: String, auth: ProjectAuthenticator?) { + self.baseURL = baseURL + self.auth.value = auth + } + + private func endpointURL(endpoint: Endpoint, params: [String: String]? = nil) -> String { + + switch endpoint { + + case .Repos: + + if let repo = params?["repo"] { + return "/2.0/repositories/\(repo)" + } else { + return "/2.0/repositories" + } + + case .PullRequests: + + assert(params?["repo"] != nil, "A repo must be specified") + let repo = self.endpointURL(.Repos, params: params) + + if let pr = params?["pr"] { + return "\(repo)/pullrequests/\(pr)" + } else { + return "\(repo)/pullrequests" + } + + case .PullRequestComments: + + assert(params?["repo"] != nil, "A repo must be specified") + assert(params?["pr"] != nil, "A PR must be specified") + let pr = self.endpointURL(.PullRequests, params: params) + + if params?["method"] == "POST" { + let repo = params!["repo"]! + let pr = params!["pr"]! + return "/1.0/repositories/\(repo)/pullrequests/\(pr)/comments" + } else { + return "\(pr)/comments" + } + + case .CommitStatuses: + + assert(params?["repo"] != nil, "A repo must be specified") + assert(params?["sha"] != nil, "A commit sha must be specified") + let repo = self.endpointURL(.Repos, params: params) + let sha = params!["sha"]! + + let build = "\(repo)/commit/\(sha)/statuses/build" + + if let key = params?["status_key"] { + return "\(build)/\(key)" + } + + return build + + } + + } + + func setAuthOnRequest(request: NSMutableURLRequest) { + + guard let auth = self.auth.value else { return } + + switch auth.type { + case .OAuthToken: + let tokens = auth.secret.componentsSeparatedByString(":") + //first is refresh token, second access token + request.setValue("Bearer \(tokens[1])", forHTTPHeaderField:"Authorization") + default: + fatalError("This kind of authentication is not supported for BitBucket") + } + } + + func createRefreshTokenRequest() -> NSMutableURLRequest { + + guard let auth = self.auth.value else { fatalError("No auth") } + let refreshUrl = auth.service.accessTokenUrl() + let refreshToken = auth.secret.componentsSeparatedByString(":")[0] + let body = [ + ("grant_type", "refresh_token"), + ("refresh_token", refreshToken) + ].map { "\($0.0)=\($0.1)" }.joinWithSeparator("&") + + let request = NSMutableURLRequest(URL: NSURL(string: refreshUrl)!) + + let service = auth.service + let servicePublicKey = service.serviceKey() + let servicePrivateKey = service.serviceSecret() + let credentials = "\(servicePublicKey):\(servicePrivateKey)".base64String() + request.setValue("Basic \(credentials)", forHTTPHeaderField:"Authorization") + + request.HTTPMethod = "POST" + self.setStringBody(request, body: body) + return request + } + + func createRequest(method: HTTP.Method, endpoint: Endpoint, params: [String : String]? = nil, query: [String : String]? = nil, body: NSDictionary? = nil) throws -> NSMutableURLRequest { + + let endpointURL = self.endpointURL(endpoint, params: params) + let queryString = HTTP.stringForQuery(query) + let wholePath = "\(self.baseURL)\(endpointURL)\(queryString)" + + let url = NSURL(string: wholePath)! + + let request = NSMutableURLRequest(URL: url) + + request.HTTPMethod = method.rawValue + self.setAuthOnRequest(request) + + if let body = body { + try self.setJSONBody(request, body: body) + } + + return request + } + + func setStringBody(request: NSMutableURLRequest, body: String) { + let data = body.dataUsingEncoding(NSUTF8StringEncoding) + request.HTTPBody = data + request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type") + } + + func setJSONBody(request: NSMutableURLRequest, body: NSDictionary) throws { + let data = try NSJSONSerialization.dataWithJSONObject(body, options: NSJSONWritingOptions()) + request.HTTPBody = data + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + } +} \ No newline at end of file diff --git a/BuildaGitServer/BitBucket/BitBucketEntity.swift b/BuildaGitServer/BitBucket/BitBucketEntity.swift new file mode 100644 index 0000000..e4ac8b1 --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketEntity.swift @@ -0,0 +1,49 @@ +// +// BitBucketEntity.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +protocol BitBucketType { + init(json: NSDictionary) +} + +class BitBucketEntity : BitBucketType { + + required init(json: NSDictionary) { + + //add any common keys to be parsed here + } + + init() { + + // + } + + func dictionarify() -> NSDictionary { + assertionFailure("Must be overriden by subclasses that wish to dictionarify their data") + return NSDictionary() + } + + class func optional(json: NSDictionary?) -> T? { + if let json = json { + return T(json: json) + } + return nil + } + +} + +//parse an array of dictionaries into an array of parsed entities +func BitBucketArray(jsonArray: [NSDictionary]) -> [T] { + + let parsed = jsonArray.map { + (json: NSDictionary) -> (T) in + return T(json: json) + } + return parsed +} diff --git a/BuildaGitServer/BitBucket/BitBucketIssue.swift b/BuildaGitServer/BitBucket/BitBucketIssue.swift new file mode 100644 index 0000000..5f0cf80 --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketIssue.swift @@ -0,0 +1,21 @@ +// +// BitBucketIssue.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class BitBucketIssue: BitBucketEntity, IssueType { + + let number: Int + + required init(json: NSDictionary) { + + self.number = json.intForKey("id") + + super.init(json: json) + } +} \ No newline at end of file diff --git a/BuildaGitServer/BitBucket/BitBucketPullRequest.swift b/BuildaGitServer/BitBucket/BitBucketPullRequest.swift new file mode 100644 index 0000000..40ddcf2 --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketPullRequest.swift @@ -0,0 +1,42 @@ +// +// BitBucketPullRequest.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class BitBucketPullRequest: BitBucketIssue, PullRequestType { + + let title: String + let source: BitBucketPullRequestBranch + let destination: BitBucketPullRequestBranch + + required init(json: NSDictionary) { + + self.title = json.stringForKey("title") + + self.source = BitBucketPullRequestBranch(json: json.dictionaryForKey("source")) + self.destination = BitBucketPullRequestBranch(json: json.dictionaryForKey("destination")) + + super.init(json: json) + } + + var headName: String { + return self.source.branch + } + + var headCommitSHA: String { + return self.source.commit + } + + var headRepo: RepoType { + return self.source.repo + } + + var baseName: String { + return self.destination.branch + } +} diff --git a/BuildaGitServer/BitBucket/BitBucketPullRequestBranch.swift b/BuildaGitServer/BitBucket/BitBucketPullRequestBranch.swift new file mode 100644 index 0000000..e559969 --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketPullRequestBranch.swift @@ -0,0 +1,25 @@ +// +// BitBucketPullRequestBranch.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class BitBucketPullRequestBranch : BitBucketEntity { + + let branch: String + let commit: String + let repo: BitBucketRepo + + required init(json: NSDictionary) { + + self.branch = json.dictionaryForKey("branch").stringForKey("name") + self.commit = json.dictionaryForKey("commit").stringForKey("hash") + self.repo = BitBucketRepo(json: json.dictionaryForKey("repository")) + + super.init(json: json) + } +} \ No newline at end of file diff --git a/BuildaGitServer/BitBucket/BitBucketRateLimit.swift b/BuildaGitServer/BitBucket/BitBucketRateLimit.swift new file mode 100644 index 0000000..1ac2a29 --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketRateLimit.swift @@ -0,0 +1,14 @@ +// +// BitBucketRateLimit.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +struct BitBucketRateLimit: RateLimitType { + + var report: String = "NO INFO" +} diff --git a/BuildaGitServer/BitBucket/BitBucketRepo.swift b/BuildaGitServer/BitBucket/BitBucketRepo.swift new file mode 100644 index 0000000..f02352a --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketRepo.swift @@ -0,0 +1,33 @@ +// +// BitBucketRepo.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class BitBucketRepo: BitBucketEntity, RepoType { + + //kind of pointless here + let permissions = RepoPermissions(read: true, write: true) + let latestRateLimitInfo: RateLimitType? = BitBucketRateLimit() + let originUrlSSH: String + + required init(json: NSDictionary) { + + //split with forward slash, the last two comps are the repo + //create a proper ssh url for bitbucket here + let repoName = json + .dictionaryForKey("links") + .dictionaryForKey("self") + .stringForKey("href") + .componentsSeparatedByString("/") + .suffix(2) + .joinWithSeparator("/") + self.originUrlSSH = "git@bitbucket.org:\(repoName).git" + + super.init(json: json) + } +} diff --git a/BuildaGitServer/BitBucket/BitBucketServer.swift b/BuildaGitServer/BitBucket/BitBucketServer.swift new file mode 100644 index 0000000..61498ef --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketServer.swift @@ -0,0 +1,368 @@ +// +// BitBucketServer.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation +import BuildaUtils +import ReactiveCocoa + +class BitBucketServer : GitServer { + + let endpoints: BitBucketEndpoints + let cache = InMemoryURLCache() + + init(endpoints: BitBucketEndpoints, http: HTTP? = nil) { + + self.endpoints = endpoints + super.init(service: .GitHub, http: http) + } + + override func authChangedSignal() -> Signal { + var res: Signal? + self.endpoints.auth.producer.startWithSignal { res = $0.0 } + return res!.observeOn(UIScheduler()) + } +} + +extension BitBucketServer: SourceServerType { + + func createStatusFromState(state: BuildState, description: String?, targetUrl: String?) -> StatusType { + + let bbState = BitBucketStatus.BitBucketState.fromBuildState(state) + let key = "Buildasaur" + let url = targetUrl ?? "https://github.com/czechboy0/Buildasaur" + return BitBucketStatus(state: bbState, key: key, name: key, description: description, url: url) + } + + func getBranchesOfRepo(repo: String, completion: (branches: [BranchType]?, error: ErrorType?) -> ()) { + + //TODO: start returning branches + completion(branches: [], error: nil) + } + + func getOpenPullRequests(repo: String, completion: (prs: [PullRequestType]?, error: ErrorType?) -> ()) { + + let params = [ + "repo": repo + ] + self._sendRequestWithMethod(.GET, endpoint: .PullRequests, params: params, query: nil, body: nil) { (response, body, error) -> () in + + if error != nil { + completion(prs: nil, error: error) + return + } + + if let body = body as? [NSDictionary] { + let prs: [BitBucketPullRequest] = BitBucketArray(body) + completion(prs: prs.map { $0 as PullRequestType }, error: nil) + } else { + completion(prs: nil, error: Error.withInfo("Wrong body \(body)")) + } + } + } + + func getPullRequest(pullRequestNumber: Int, repo: String, completion: (pr: PullRequestType?, error: ErrorType?) -> ()) { + + let params = [ + "repo": repo, + "pr": pullRequestNumber.description + ] + + self._sendRequestWithMethod(.GET, endpoint: .PullRequests, params: params, query: nil, body: nil) { (response, body, error) -> () in + + if error != nil { + completion(pr: nil, error: error) + return + } + + if let body = body as? NSDictionary { + let pr = BitBucketPullRequest(json: body) + completion(pr: pr, error: nil) + } else { + completion(pr: nil, error: Error.withInfo("Wrong body \(body)")) + } + } + } + + func getRepo(repo: String, completion: (repo: RepoType?, error: ErrorType?) -> ()) { + + let params = [ + "repo": repo + ] + + self._sendRequestWithMethod(.GET, endpoint: .Repos, params: params, query: nil, body: nil) { + (response, body, error) -> () in + + if error != nil { + completion(repo: nil, error: error) + return + } + + if let body = body as? NSDictionary { + let repository = BitBucketRepo(json: body) + completion(repo: repository, error: nil) + } else { + completion(repo: nil, error: Error.withInfo("Wrong body \(body)")) + } + } + } + + func getStatusOfCommit(commit: String, repo: String, completion: (status: StatusType?, error: ErrorType?) -> ()) { + + let params = [ + "repo": repo, + "sha": commit, + "status_key": "Buildasaur" + ] + + self._sendRequestWithMethod(.GET, endpoint: .CommitStatuses, params: params, query: nil, body: nil) { (response, body, error) -> () in + + if response?.statusCode == 404 { + //no status yet, just pass nil but OK + completion(status: nil, error: nil) + return + } + + if error != nil { + completion(status: nil, error: error) + return + } + + if let body = body as? NSDictionary { + let status = BitBucketStatus(json: body) + completion(status: status, error: nil) + } else { + completion(status: nil, error: Error.withInfo("Wrong body \(body)")) + } + } + } + + func postStatusOfCommit(commit: String, status: StatusType, repo: String, completion: (status: StatusType?, error: ErrorType?) -> ()) { + + let params = [ + "repo": repo, + "sha": commit + ] + + let body = (status as! BitBucketStatus).dictionarify() + self._sendRequestWithMethod(.POST, endpoint: .CommitStatuses, params: params, query: nil, body: body) { (response, body, error) -> () in + + if error != nil { + completion(status: nil, error: error) + return + } + + if let body = body as? NSDictionary { + let status = BitBucketStatus(json: body) + completion(status: status, error: nil) + } else { + completion(status: nil, error: Error.withInfo("Wrong body \(body)")) + } + } + } + + func postCommentOnIssue(comment: String, issueNumber: Int, repo: String, completion: (comment: CommentType?, error: ErrorType?) -> ()) { + + let params = [ + "repo": repo, + "pr": issueNumber.description + ] + + let body = [ + "content": comment + ] + + self._sendRequestWithMethod(.POST, endpoint: .PullRequestComments, params: params, query: nil, body: body) { (response, body, error) -> () in + + if error != nil { + completion(comment: nil, error: error) + return + } + + if let body = body as? NSDictionary { + let comment = BitBucketComment(json: body) + completion(comment: comment, error: nil) + } else { + completion(comment: nil, error: Error.withInfo("Wrong body \(body)")) + } + } + } + + func getCommentsOfIssue(issueNumber: Int, repo: String, completion: (comments: [CommentType]?, error: ErrorType?) -> ()) { + + let params = [ + "repo": repo, + "pr": issueNumber.description + ] + + self._sendRequestWithMethod(.GET, endpoint: .PullRequestComments, params: params, query: nil, body: nil) { (response, body, error) -> () in + + if error != nil { + completion(comments: nil, error: error) + return + } + + if let body = body as? [NSDictionary] { + let comments: [BitBucketComment] = BitBucketArray(body) + completion(comments: comments.map { $0 as CommentType }, error: nil) + } else { + completion(comments: nil, error: Error.withInfo("Wrong body \(body)")) + } + } + } +} + +extension BitBucketServer { + + private func _sendRequest(request: NSMutableURLRequest, isRetry: Bool = false, completion: HTTP.Completion) { + + self.http.sendRequest(request) { (response, body, error) -> () in + + if let error = error { + completion(response: response, body: body, error: error) + return + } + + //error out on special HTTP status codes + let statusCode = response!.statusCode + switch statusCode { + case 401: //unauthorized, use refresh token to get a new access token + //only try to refresh token once + if !isRetry { + self._handle401(request, completion: completion) + } + return + case 400, 402 ... 500: + + let message = ((body as? NSDictionary)?["error"] as? NSDictionary)?["message"] as? String ?? (body as? String ?? "Unknown error") + let resultString = "\(statusCode): \(message)" + completion(response: response, body: body, error: Error.withInfo(resultString, internalError: error)) + return + default: + break + } + + completion(response: response, body: body, error: error) + } + } + + private func _handle401(request: NSMutableURLRequest, completion: HTTP.Completion) { + + //we need to use the refresh token to request a new access token + //then we need to notify that we updated the secret, so that it can + //be saved by buildasaur + //then we need to set the new access token to this waiting request and + //run it again. if that fails too, we fail for real. + + Log.verbose("Got 401, starting a BitBucket refresh token flow...") + + //get a new access token + self._refreshAccessToken(request) { error in + + if let error = error { + Log.verbose("Failed to get a new access token") + completion(response: nil, body: nil, error: error) + return + } + + //we have a new access token, force set the new cred on the original + //request + self.endpoints.setAuthOnRequest(request) + + Log.verbose("Successfully refreshed a BitBucket access token") + + //retrying the original request + self._sendRequest(request, isRetry: true, completion: completion) + } + } + + private func _refreshAccessToken(request: NSMutableURLRequest, completion: (NSError?) -> ()) { + + let refreshRequest = self.endpoints.createRefreshTokenRequest() + self.http.sendRequest(refreshRequest) { (response, body, error) -> () in + + if let error = error { + completion(error) + return + } + + guard response?.statusCode == 200 else { + completion(Error.withInfo("Wrong status code returned, refreshing access token failed")) + return + } + + let payload = body as! NSDictionary + let accessToken = payload.stringForKey("access_token") + let refreshToken = payload.stringForKey("refresh_token") + let secret = [refreshToken, accessToken].joinWithSeparator(":") + + let newAuth = ProjectAuthenticator(service: .BitBucket, username: "GIT", type: .OAuthToken, secret: secret) + self.endpoints.auth.value = newAuth + completion(nil) + } + } + + private func _sendRequestWithMethod(method: HTTP.Method, endpoint: BitBucketEndpoints.Endpoint, params: [String: String]?, query: [String: String]?, body: NSDictionary?, completion: HTTP.Completion) { + + var allParams = [ + "method": method.rawValue + ] + + //merge the two params + if let params = params { + for (key, value) in params { + allParams[key] = value + } + } + + do { + let request = try self.endpoints.createRequest(method, endpoint: endpoint, params: allParams, query: query, body: body) + self._sendRequestWithPossiblePagination(request, accumulatedResponseBody: NSArray(), completion: completion) + } catch { + completion(response: nil, body: nil, error: Error.withInfo("Couldn't create Request, error \(error)")) + } + } + + private func _sendRequestWithPossiblePagination(request: NSMutableURLRequest, accumulatedResponseBody: NSArray, completion: HTTP.Completion) { + + self._sendRequest(request) { + (response, body, error) -> () in + + if error != nil { + completion(response: response, body: body, error: error) + return + } + + guard let dictBody = body as? NSDictionary else { + completion(response: response, body: body, error: error) + return + } + + //pull out the values + guard let arrayBody = dictBody["values"] as? [AnyObject] else { + completion(response: response, body: dictBody, error: error) + return + } + + //we do have more, let's fetch it + let newBody = accumulatedResponseBody.arrayByAddingObjectsFromArray(arrayBody) + + guard let nextLink = dictBody.optionalStringForKey("next") else { + + //is array, but we don't have any more data + completion(response: response, body: newBody, error: error) + return + } + + let newRequest = request.mutableCopy() as! NSMutableURLRequest + newRequest.URL = NSURL(string: nextLink)! + self._sendRequestWithPossiblePagination(newRequest, accumulatedResponseBody: newBody, completion: completion) + return + } + } + +} diff --git a/BuildaGitServer/BitBucket/BitBucketStatus.swift b/BuildaGitServer/BitBucket/BitBucketStatus.swift new file mode 100644 index 0000000..9807080 --- /dev/null +++ b/BuildaGitServer/BitBucket/BitBucketStatus.swift @@ -0,0 +1,82 @@ +// +// BitBucketStatus.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class BitBucketStatus: BitBucketEntity, StatusType { + + enum BitBucketState: String { + case InProgress = "INPROGRESS" + case Success = "SUCCESSFUL" + case Failed = "FAILED" + } + + let bbState: BitBucketState + let key: String + let name: String? + let description: String? + let targetUrl: String? + + required init(json: NSDictionary) { + + self.bbState = BitBucketState(rawValue: json.stringForKey("state"))! + self.key = json.stringForKey("key") + self.name = json.optionalStringForKey("name") + self.description = json.optionalStringForKey("description") + self.targetUrl = json.stringForKey("url") + + super.init(json: json) + } + + init(state: BitBucketState, key: String, name: String?, description: String?, url: String) { + + self.bbState = state + self.key = key + self.name = name + self.description = description + self.targetUrl = url + + super.init() + } + + var state: BuildState { + return self.bbState.toBuildState() + } + + override func dictionarify() -> NSDictionary { + + let dictionary = NSMutableDictionary() + + dictionary["state"] = self.bbState.rawValue + dictionary["key"] = self.key + dictionary.optionallyAddValueForKey(self.description, key: "description") + dictionary.optionallyAddValueForKey(self.name, key: "name") + dictionary.optionallyAddValueForKey(self.targetUrl, key: "url") + + return dictionary.copy() as! NSDictionary + } +} + +extension BitBucketStatus.BitBucketState { + + static func fromBuildState(state: BuildState) -> BitBucketStatus.BitBucketState { + switch state { + case .Success, .NoState: return .Success + case .Pending: return .InProgress + case .Error, .Failure: return .Failed + } + } + + func toBuildState() -> BuildState { + switch self { + case .Success: return .Success + case .InProgress: return .Pending + case .Failed: return .Failure + } + } +} diff --git a/BuildaGitServer/Branch.swift b/BuildaGitServer/Branch.swift deleted file mode 100644 index 5ac8bac..0000000 --- a/BuildaGitServer/Branch.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Branch.swift -// Buildasaur -// -// Created by Honza Dvorsky on 13/12/2014. -// Copyright (c) 2014 Honza Dvorsky. All rights reserved. -// - -import Foundation - -public class Branch : GitHubEntity { - - public let name: String - public let commit: Commit - - public required init(json: NSDictionary) { - - self.name = json.stringForKey("name") - self.commit = Commit(json: json.dictionaryForKey("commit")) - super.init(json: json) - } -} diff --git a/BuildaGitServer/Comment.swift b/BuildaGitServer/Comment.swift deleted file mode 100644 index 89014ae..0000000 --- a/BuildaGitServer/Comment.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// Comment.swift -// Buildasaur -// -// Created by Honza Dvorsky on 13/12/2014. -// Copyright (c) 2014 Honza Dvorsky. All rights reserved. -// - -import Foundation - -public class Comment : GitHubEntity { - - public let body: String - public let author: User - - public required init(json: NSDictionary) { - - self.body = json.stringForKey("body") - self.author = User(json: json.dictionaryForKey("user")) - - super.init(json: json) - } -} - diff --git a/BuildaGitServer/Extensions.swift b/BuildaGitServer/Extensions.swift new file mode 100644 index 0000000..9517028 --- /dev/null +++ b/BuildaGitServer/Extensions.swift @@ -0,0 +1,19 @@ +// +// Extensions.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/28/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation + +extension String { + + public func base64String() -> String { + return self + .dataUsingEncoding(NSUTF8StringEncoding)! + .base64EncodedStringWithOptions(NSDataBase64EncodingOptions()) + } +} + diff --git a/BuildaGitServer/GitHub/GitHubBranch.swift b/BuildaGitServer/GitHub/GitHubBranch.swift new file mode 100644 index 0000000..84948bd --- /dev/null +++ b/BuildaGitServer/GitHub/GitHubBranch.swift @@ -0,0 +1,32 @@ +// +// GitHubBranch.swift +// Buildasaur +// +// Created by Honza Dvorsky on 13/12/2014. +// Copyright (c) 2014 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class GitHubBranch : GitHubEntity { + + let name: String + let commit: GitHubCommit + + required init(json: NSDictionary) { + + self.name = json.stringForKey("name") + self.commit = GitHubCommit(json: json.dictionaryForKey("commit")) + super.init(json: json) + } +} + +extension GitHubBranch: BranchType { + + //name (see above) + + var commitSHA: String { + return self.commit.sha + } + +} diff --git a/BuildaGitServer/GitHub/GitHubComment.swift b/BuildaGitServer/GitHub/GitHubComment.swift new file mode 100644 index 0000000..b0e2b92 --- /dev/null +++ b/BuildaGitServer/GitHub/GitHubComment.swift @@ -0,0 +1,27 @@ +// +// GitHubComment.swift +// Buildasaur +// +// Created by Honza Dvorsky on 13/12/2014. +// Copyright (c) 2014 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class GitHubComment : GitHubEntity { + + let body: String + let author: GitHubUser + + required init(json: NSDictionary) { + + self.body = json.stringForKey("body") + self.author = GitHubUser(json: json.dictionaryForKey("user")) + + super.init(json: json) + } +} + +extension GitHubComment: CommentType { + +} diff --git a/BuildaGitServer/Commit.swift b/BuildaGitServer/GitHub/GitHubCommit.swift similarity index 74% rename from BuildaGitServer/Commit.swift rename to BuildaGitServer/GitHub/GitHubCommit.swift index c884cd7..e61eb48 100644 --- a/BuildaGitServer/Commit.swift +++ b/BuildaGitServer/GitHub/GitHubCommit.swift @@ -1,5 +1,5 @@ // -// Commit.swift +// GitHubCommit.swift // Buildasaur // // Created by Honza Dvorsky on 13/12/2014. @@ -9,11 +9,11 @@ import Foundation //GitHub commit in all its glory, with git commit metadata, plus comments, committer, author and parents info -public class Commit : GitHubEntity { +class GitHubCommit : GitHubEntity { - public let sha: String + let sha: String - public required init(json: NSDictionary) { + required init(json: NSDictionary) { self.sha = json.stringForKey("sha") diff --git a/BuildaGitServer/GitHubEndpoints.swift b/BuildaGitServer/GitHub/GitHubEndpoints.swift similarity index 88% rename from BuildaGitServer/GitHubEndpoints.swift rename to BuildaGitServer/GitHub/GitHubEndpoints.swift index 5d12e27..ca43d94 100644 --- a/BuildaGitServer/GitHubEndpoints.swift +++ b/BuildaGitServer/GitHub/GitHubEndpoints.swift @@ -9,9 +9,9 @@ import Foundation import BuildaUtils -public class GitHubEndpoints { +class GitHubEndpoints { - public enum Endpoint { + enum Endpoint { case Users case Repos case PullRequests @@ -23,7 +23,7 @@ public class GitHubEndpoints { case Merges } - public enum MergeResult { + enum MergeResult { case Success(NSDictionary) case NothingToMerge case Conflict @@ -31,11 +31,11 @@ public class GitHubEndpoints { } private let baseURL: String - private let token: String? + private let auth: ProjectAuthenticator? - public init(baseURL: String, token: String?) { + init(baseURL: String, auth: ProjectAuthenticator?) { self.baseURL = baseURL - self.token = token + self.auth = auth } private func endpointURL(endpoint: Endpoint, params: [String: String]? = nil) -> String { @@ -140,8 +140,7 @@ public class GitHubEndpoints { } } - - public func createRequest(method:HTTP.Method, endpoint:Endpoint, params: [String : String]? = nil, query: [String : String]? = nil, body:NSDictionary? = nil) throws -> NSMutableURLRequest { + func createRequest(method:HTTP.Method, endpoint:Endpoint, params: [String : String]? = nil, query: [String : String]? = nil, body:NSDictionary? = nil) throws -> NSMutableURLRequest { let endpointURL = self.endpointURL(endpoint, params: params) let queryString = HTTP.stringForQuery(query) @@ -152,8 +151,12 @@ public class GitHubEndpoints { let request = NSMutableURLRequest(URL: url) request.HTTPMethod = method.rawValue - if let token = self.token { - request.setValue("token \(token)", forHTTPHeaderField:"Authorization") + if let auth = self.auth { + + switch auth.type { + case .PersonalToken, .OAuthToken: + request.setValue("token \(auth.secret)", forHTTPHeaderField:"Authorization") + } } if let body = body { diff --git a/BuildaGitServer/GitHubEntity.swift b/BuildaGitServer/GitHub/GitHubEntity.swift similarity index 71% rename from BuildaGitServer/GitHubEntity.swift rename to BuildaGitServer/GitHub/GitHubEntity.swift index d2ac153..be07638 100644 --- a/BuildaGitServer/GitHubEntity.swift +++ b/BuildaGitServer/GitHub/GitHubEntity.swift @@ -8,36 +8,36 @@ import Foundation -public protocol GitHub { +protocol GitHubType { init(json: NSDictionary) } -public class GitHubEntity : GitHub { +class GitHubEntity : GitHubType { - public let htmlUrl: String? - public let url: String? - public let id: Int? + let htmlUrl: String? + let url: String? + let id: Int? //initializer which takes a dictionary and fills in values for recognized keys - public required init(json: NSDictionary) { + required init(json: NSDictionary) { self.htmlUrl = json.optionalStringForKey("html_url") self.url = json.optionalStringForKey("url") self.id = json.optionalIntForKey("id") } - public init() { + init() { self.htmlUrl = nil self.url = nil self.id = nil } - public func dictionarify() -> NSDictionary { + func dictionarify() -> NSDictionary { assertionFailure("Must be overriden by subclasses that wish to dictionarify their data") return NSDictionary() } - public class func optional(json: NSDictionary?) -> T? { + class func optional(json: NSDictionary?) -> T? { if let json = json { return T(json: json) } @@ -47,7 +47,7 @@ public class GitHubEntity : GitHub { } //parse an array of dictionaries into an array of parsed entities -public func GitHubArray(jsonArray: NSArray!) -> [T] { +func GitHubArray(jsonArray: NSArray!) -> [T] { let array = jsonArray as! [NSDictionary]! let parsed = array.map { diff --git a/BuildaGitServer/Issue.swift b/BuildaGitServer/GitHub/GitHubIssue.swift similarity index 66% rename from BuildaGitServer/Issue.swift rename to BuildaGitServer/GitHub/GitHubIssue.swift index 8d08821..2d7e268 100644 --- a/BuildaGitServer/Issue.swift +++ b/BuildaGitServer/GitHub/GitHubIssue.swift @@ -1,5 +1,5 @@ // -// Issue.swift +// GitHubIssue.swift // Buildasaur // // Created by Honza Dvorsky on 13/12/2014. @@ -8,13 +8,13 @@ import Foundation -public class Issue : GitHubEntity { +class GitHubIssue : GitHubEntity { - public let number: Int - public let body: String - public let title: String + let number: Int + let body: String + var title: String - public required init(json: NSDictionary) { + required init(json: NSDictionary) { self.number = json.intForKey("number") self.body = json.stringForKey("body") diff --git a/BuildaGitServer/GitHub/GitHubPullRequest.swift b/BuildaGitServer/GitHub/GitHubPullRequest.swift new file mode 100644 index 0000000..6528186 --- /dev/null +++ b/BuildaGitServer/GitHub/GitHubPullRequest.swift @@ -0,0 +1,39 @@ +// +// GitHubPullRequest.swift +// Buildasaur +// +// Created by Honza Dvorsky on 13/12/2014. +// Copyright (c) 2014 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class GitHubPullRequest : GitHubIssue, PullRequestType { + + let head: GitHubPullRequestBranch + let base: GitHubPullRequestBranch + + required init(json: NSDictionary) { + + self.head = GitHubPullRequestBranch(json: json.dictionaryForKey("head")) + self.base = GitHubPullRequestBranch(json: json.dictionaryForKey("base")) + + super.init(json: json) + } + + var headName: String { + return self.head.ref + } + + var headCommitSHA: String { + return self.head.sha + } + + var headRepo: RepoType { + return self.head.repo + } + + var baseName: String { + return self.base.ref + } +} diff --git a/BuildaGitServer/PullRequestBranch.swift b/BuildaGitServer/GitHub/GitHubPullRequestBranch.swift similarity index 64% rename from BuildaGitServer/PullRequestBranch.swift rename to BuildaGitServer/GitHub/GitHubPullRequestBranch.swift index 9a2a360..48660a0 100644 --- a/BuildaGitServer/PullRequestBranch.swift +++ b/BuildaGitServer/GitHub/GitHubPullRequestBranch.swift @@ -1,5 +1,5 @@ // -// Branch.swift +// GitHubBranch.swift // Buildasaur // // Created by Honza Dvorsky on 13/12/2014. @@ -10,17 +10,17 @@ import Foundation //PullRequestBranch is a special type of a branch - it also includes repo info (bc PRs can be cross repos) //normal branches include less information -public class PullRequestBranch : GitHubEntity { +class GitHubPullRequestBranch : GitHubEntity { - public let ref: String - public let sha: String - public let repo: Repo + let ref: String + let sha: String + let repo: GitHubRepo - public required init(json: NSDictionary) { + required init(json: NSDictionary) { self.ref = json.stringForKey("ref") self.sha = json.stringForKey("sha") - self.repo = Repo(json: json.dictionaryForKey("repo")) + self.repo = GitHubRepo(json: json.dictionaryForKey("repo")) super.init(json: json) } diff --git a/BuildaGitServer/GitHubRateLimit.swift b/BuildaGitServer/GitHub/GitHubRateLimit.swift similarity index 80% rename from BuildaGitServer/GitHubRateLimit.swift rename to BuildaGitServer/GitHub/GitHubRateLimit.swift index d11f9d8..77f9c9f 100644 --- a/BuildaGitServer/GitHubRateLimit.swift +++ b/BuildaGitServer/GitHub/GitHubRateLimit.swift @@ -8,14 +8,14 @@ import Foundation -public struct GitHubRateLimit { +struct GitHubRateLimit { - public let resetTime: Double - public let limit: Int - public let remaining: Int - public let now: Double = NSDate().timeIntervalSince1970 + let resetTime: Double + let limit: Int + let remaining: Int + let now: Double = NSDate().timeIntervalSince1970 - public func getReport() -> String { + func getReport() -> String { let resetInterval = 3600.0 //reset interval is 1 hour let startTime = self.resetTime - resetInterval @@ -33,5 +33,11 @@ public struct GitHubRateLimit { let report = "count: \(consumed)/\(self.limit), renews in \(Int(remainingTime)) seconds, rate: \(rateOfConsumptionPretty)/\(maxRateOfConsumptionPretty), using \(usedRatePercent)% of the allowed request rate." return report } +} + +extension GitHubRateLimit: RateLimitType { + var report: String { + return self.getReport() + } } diff --git a/BuildaGitServer/GitHub/GitHubRepo.swift b/BuildaGitServer/GitHub/GitHubRepo.swift new file mode 100644 index 0000000..c07f407 --- /dev/null +++ b/BuildaGitServer/GitHub/GitHubRepo.swift @@ -0,0 +1,51 @@ +// +// GitHubRepo.swift +// Buildasaur +// +// Created by Honza Dvorsky on 13/12/2014. +// Copyright (c) 2014 Honza Dvorsky. All rights reserved. +// + +import Foundation + +class GitHubRepo : GitHubEntity { + + let name: String + let fullName: String + let repoUrlHTTPS: String + let repoUrlSSH: String + let permissionsDict: NSDictionary + + var latestRateLimitInfo: RateLimitType? + + required init(json: NSDictionary) { + + self.name = json.stringForKey("name") + self.fullName = json.stringForKey("full_name") + self.repoUrlHTTPS = json.stringForKey("clone_url") + self.repoUrlSSH = json.stringForKey("ssh_url") + + if let permissions = json.optionalDictionaryForKey("permissions") { + self.permissionsDict = permissions + } else { + self.permissionsDict = NSDictionary() + } + + super.init(json: json) + } +} + +extension GitHubRepo: RepoType { + + var permissions: RepoPermissions { + + let read = self.permissionsDict["pull"] as? Bool ?? false + let write = self.permissionsDict["push"] as? Bool ?? false + return RepoPermissions(read: read, write: write) + } + + var originUrlSSH: String { + + return self.repoUrlSSH + } +} diff --git a/BuildaGitServer/GitHubServer.swift b/BuildaGitServer/GitHub/GitHubServer.swift similarity index 67% rename from BuildaGitServer/GitHubServer.swift rename to BuildaGitServer/GitHub/GitHubServer.swift index b01d4af..c94af33 100644 --- a/BuildaGitServer/GitHubServer.swift +++ b/BuildaGitServer/GitHub/GitHubServer.swift @@ -8,28 +8,95 @@ import Foundation import BuildaUtils +import ReactiveCocoa -public class GitHubServer : GitServer { +class GitHubServer : GitServer { - public let endpoints: GitHubEndpoints - public var latestRateLimitInfo: GitHubRateLimit? - - let cache = InMemoryURLCache() + let endpoints: GitHubEndpoints + var latestRateLimitInfo: GitHubRateLimit? + + let cache = InMemoryURLCache() - public init(endpoints: GitHubEndpoints, http: HTTP? = nil) { + init(endpoints: GitHubEndpoints, http: HTTP? = nil) { self.endpoints = endpoints - super.init(http: http) + super.init(service: .GitHub, http: http) } } //TODO: from each of these calls, return a "cancellable" object which can be used for cancelling +extension GitHubServer: SourceServerType { + + func getBranchesOfRepo(repo: String, completion: (branches: [BranchType]?, error: ErrorType?) -> ()) { + + self._getBranchesOfRepo(repo) { (branches, error) -> () in + completion(branches: branches?.map { $0 as BranchType }, error: error) + } + } + + func getOpenPullRequests(repo: String, completion: (prs: [PullRequestType]?, error: ErrorType?) -> ()) { + + self._getOpenPullRequests(repo) { (prs, error) -> () in + completion(prs: prs?.map { $0 as PullRequestType }, error: error) + } + } + + func getPullRequest(pullRequestNumber: Int, repo: String, completion: (pr: PullRequestType?, error: ErrorType?) -> ()) { + + self._getPullRequest(pullRequestNumber, repo: repo) { (pr, error) -> () in + completion(pr: pr, error: error) + } + } + + func getRepo(repo: String, completion: (repo: RepoType?, error: ErrorType?) -> ()) { + + self._getRepo(repo) { (repo, error) -> () in + completion(repo: repo, error: error) + } + } + + func getStatusOfCommit(commit: String, repo: String, completion: (status: StatusType?, error: ErrorType?) -> ()) { + + self._getStatusOfCommit(commit, repo: repo) { (status, error) -> () in + completion(status: status, error: error) + } + } + + func postStatusOfCommit(commit: String, status: StatusType, repo: String, completion: (status: StatusType?, error: ErrorType?) -> ()) { + + self._postStatusOfCommit(status as! GitHubStatus, sha: commit, repo: repo) { (status, error) -> () in + completion(status: status, error: error) + } + } + + func postCommentOnIssue(comment: String, issueNumber: Int, repo: String, completion: (comment: CommentType?, error: ErrorType?) -> ()) { + + self._postCommentOnIssue(comment, issueNumber: issueNumber, repo: repo) { (comment, error) -> () in + completion(comment: comment, error: error) + } + } + + func getCommentsOfIssue(issueNumber: Int, repo: String, completion: (comments: [CommentType]?, error: ErrorType?) -> ()) { + + self._getCommentsOfIssue(issueNumber, repo: repo) { (comments, error) -> () in + completion(comments: comments?.map { $0 as CommentType }, error: error) + } + } + + func createStatusFromState(buildState: BuildState, description: String?, targetUrl: String?) -> StatusType { + + let state = GitHubStatus.GitHubState.fromBuildState(buildState) + let context = "Buildasaur" + return GitHubStatus(state: state, description: description, targetUrl: targetUrl, context: context) + } +} + extension GitHubServer { - private func sendRequestWithPossiblePagination(request: NSMutableURLRequest, accumulatedResponseBody: NSArray, completion: HTTP.Completion) { + private func _sendRequestWithPossiblePagination(request: NSMutableURLRequest, accumulatedResponseBody: NSArray, completion: HTTP.Completion) { - self.sendRequest(request) { + self._sendRequest(request) { (response, body, error) -> () in if error != nil { @@ -44,7 +111,7 @@ extension GitHubServer { if let links = response?.allHeaderFields["Link"] as? String { //now parse page links - if let pageInfo = self.parsePageLinks(links) { + if let pageInfo = self._parsePageLinks(links) { //here look at the links and go to the next page, accumulate the body from this response //and pass it through @@ -53,7 +120,7 @@ extension GitHubServer { let newRequest = request.mutableCopy() as! NSMutableURLRequest newRequest.URL = nextUrl - self.sendRequestWithPossiblePagination(newRequest, accumulatedResponseBody: newBody, completion: completion) + self._sendRequestWithPossiblePagination(newRequest, accumulatedResponseBody: newBody, completion: completion) return } } @@ -73,7 +140,7 @@ extension GitHubServer { case Last = "last" } - private func parsePageLinks(links: String) -> [RelPage: NSURL]? { + private func _parsePageLinks(links: String) -> [RelPage: NSURL]? { var linkDict = [RelPage: NSURL]() @@ -115,7 +182,7 @@ extension GitHubServer { return linkDict } - private func sendRequest(request: NSMutableURLRequest, completion: HTTP.Completion) { + private func _sendRequest(request: NSMutableURLRequest, completion: HTTP.Completion) { let cachedInfo = self.cache.getCachedInfoForRequest(request) if let etag = cachedInfo.etag { @@ -183,7 +250,7 @@ extension GitHubServer { }) } - private func sendRequestWithMethod(method: HTTP.Method, endpoint: GitHubEndpoints.Endpoint, params: [String: String]?, query: [String: String]?, body: NSDictionary?, completion: HTTP.Completion) { + private func _sendRequestWithMethod(method: HTTP.Method, endpoint: GitHubEndpoints.Endpoint, params: [String: String]?, query: [String: String]?, body: NSDictionary?, completion: HTTP.Completion) { var allParams = [ "method": method.rawValue @@ -198,7 +265,7 @@ extension GitHubServer { do { let request = try self.endpoints.createRequest(method, endpoint: endpoint, params: allParams, query: query, body: body) - self.sendRequestWithPossiblePagination(request, accumulatedResponseBody: NSArray(), completion: completion) + self._sendRequestWithPossiblePagination(request, accumulatedResponseBody: NSArray(), completion: completion) } catch { completion(response: nil, body: nil, error: Error.withInfo("Couldn't create Request, error \(error)")) } @@ -207,12 +274,12 @@ extension GitHubServer { /** * GET all open pull requests of a repo (full name). */ - public func getOpenPullRequests(repo: String, completion: (prs: [PullRequest]?, error: NSError?) -> ()) { + private func _getOpenPullRequests(repo: String, completion: (prs: [GitHubPullRequest]?, error: NSError?) -> ()) { let params = [ "repo": repo ] - self.sendRequestWithMethod(.GET, endpoint: .PullRequests, params: params, query: nil, body: nil) { (response, body, error) -> () in + self._sendRequestWithMethod(.GET, endpoint: .PullRequests, params: params, query: nil, body: nil) { (response, body, error) -> () in if error != nil { completion(prs: nil, error: error) @@ -220,7 +287,7 @@ extension GitHubServer { } if let body = body as? NSArray { - let prs: [PullRequest] = GitHubArray(body) + let prs: [GitHubPullRequest] = GitHubArray(body) completion(prs: prs, error: nil) } else { completion(prs: nil, error: Error.withInfo("Wrong body \(body)")) @@ -231,14 +298,14 @@ extension GitHubServer { /** * GET a pull requests of a repo (full name) by its number. */ - public func getPullRequest(pullRequestNumber: Int, repo: String, completion: (pr: PullRequest?, error: NSError?) -> ()) { + private func _getPullRequest(pullRequestNumber: Int, repo: String, completion: (pr: GitHubPullRequest?, error: NSError?) -> ()) { let params = [ "repo": repo, "pr": pullRequestNumber.description ] - self.sendRequestWithMethod(.GET, endpoint: .PullRequests, params: params, query: nil, body: nil) { (response, body, error) -> () in + self._sendRequestWithMethod(.GET, endpoint: .PullRequests, params: params, query: nil, body: nil) { (response, body, error) -> () in if error != nil { completion(pr: nil, error: error) @@ -246,7 +313,7 @@ extension GitHubServer { } if let body = body as? NSDictionary { - let pr = PullRequest(json: body) + let pr = GitHubPullRequest(json: body) completion(pr: pr, error: nil) } else { completion(pr: nil, error: Error.withInfo("Wrong body \(body)")) @@ -257,12 +324,12 @@ extension GitHubServer { /** * GET all open issues of a repo (full name). */ - public func getOpenIssues(repo: String, completion: (issues: [Issue]?, error: NSError?) -> ()) { + private func _getOpenIssues(repo: String, completion: (issues: [GitHubIssue]?, error: NSError?) -> ()) { let params = [ "repo": repo ] - self.sendRequestWithMethod(.GET, endpoint: .Issues, params: params, query: nil, body: nil) { (response, body, error) -> () in + self._sendRequestWithMethod(.GET, endpoint: .Issues, params: params, query: nil, body: nil) { (response, body, error) -> () in if error != nil { completion(issues: nil, error: error) @@ -270,7 +337,7 @@ extension GitHubServer { } if let body = body as? NSArray { - let issues: [Issue] = GitHubArray(body) + let issues: [GitHubIssue] = GitHubArray(body) completion(issues: issues, error: nil) } else { completion(issues: nil, error: Error.withInfo("Wrong body \(body)")) @@ -281,14 +348,14 @@ extension GitHubServer { /** * GET an issue of a repo (full name) by its number. */ - public func getIssue(issueNumber: Int, repo: String, completion: (issue: Issue?, error: NSError?) -> ()) { + private func _getIssue(issueNumber: Int, repo: String, completion: (issue: GitHubIssue?, error: NSError?) -> ()) { let params = [ "repo": repo, "issue": issueNumber.description ] - self.sendRequestWithMethod(.GET, endpoint: .Issues, params: params, query: nil, body: nil) { (response, body, error) -> () in + self._sendRequestWithMethod(.GET, endpoint: .Issues, params: params, query: nil, body: nil) { (response, body, error) -> () in if error != nil { completion(issue: nil, error: error) @@ -296,7 +363,7 @@ extension GitHubServer { } if let body = body as? NSDictionary { - let issue = Issue(json: body) + let issue = GitHubIssue(json: body) completion(issue: issue, error: nil) } else { completion(issue: nil, error: Error.withInfo("Wrong body \(body)")) @@ -307,7 +374,7 @@ extension GitHubServer { /** * POST a new Issue */ - public func postNewIssue(issueTitle: String, issueBody: String?, repo: String, completion: (issue: Issue?, error: NSError?) -> ()) { + private func _postNewIssue(issueTitle: String, issueBody: String?, repo: String, completion: (issue: GitHubIssue?, error: NSError?) -> ()) { let params = [ "repo": repo, @@ -318,7 +385,7 @@ extension GitHubServer { "body": issueBody ?? "" ] - self.sendRequestWithMethod(.POST, endpoint: .Issues, params: params, query: nil, body: body) { (response, body, error) -> () in + self._sendRequestWithMethod(.POST, endpoint: .Issues, params: params, query: nil, body: body) { (response, body, error) -> () in if error != nil { completion(issue: nil, error: error) @@ -326,7 +393,7 @@ extension GitHubServer { } if let body = body as? NSDictionary { - let issue = Issue(json: body) + let issue = GitHubIssue(json: body) completion(issue: issue, error: nil) } else { completion(issue: nil, error: Error.withInfo("Wrong body \(body)")) @@ -337,7 +404,7 @@ extension GitHubServer { /** * Close an Issue by its number and repo (full name). */ - public func closeIssue(issueNumber: Int, repo: String, completion: (issue: Issue?, error: NSError?) -> ()) { + private func _closeIssue(issueNumber: Int, repo: String, completion: (issue: GitHubIssue?, error: NSError?) -> ()) { let params = [ "repo": repo, @@ -348,7 +415,7 @@ extension GitHubServer { "state": "closed" ] - self.sendRequestWithMethod(.PATCH, endpoint: .Issues, params: params, query: nil, body: body) { (response, body, error) -> () in + self._sendRequestWithMethod(.PATCH, endpoint: .Issues, params: params, query: nil, body: body) { (response, body, error) -> () in if error != nil { completion(issue: nil, error: error) @@ -356,7 +423,7 @@ extension GitHubServer { } if let body = body as? NSDictionary { - let issue = Issue(json: body) + let issue = GitHubIssue(json: body) completion(issue: issue, error: nil) } else { completion(issue: nil, error: Error.withInfo("Wrong body \(body)")) @@ -367,14 +434,14 @@ extension GitHubServer { /** * GET the status of a commit (sha) from a repo. */ - public func getStatusOfCommit(sha: String, repo: String, completion: (status: Status?, error: NSError?) -> ()) { + private func _getStatusOfCommit(sha: String, repo: String, completion: (status: GitHubStatus?, error: NSError?) -> ()) { let params = [ "repo": repo, "sha": sha ] - self.sendRequestWithMethod(.GET, endpoint: .Statuses, params: params, query: nil, body: nil) { (response, body, error) -> () in + self._sendRequestWithMethod(.GET, endpoint: .Statuses, params: params, query: nil, body: nil) { (response, body, error) -> () in if error != nil { completion(status: nil, error: error) @@ -382,7 +449,7 @@ extension GitHubServer { } if let body = body as? NSArray { - let statuses: [Status] = GitHubArray(body) + let statuses: [GitHubStatus] = GitHubArray(body) //sort them by creation date let mostRecentStatus = statuses.sort({ return $0.created! > $1.created! }).first completion(status: mostRecentStatus, error: nil) @@ -395,7 +462,7 @@ extension GitHubServer { /** * POST a new status on a commit. */ - public func postStatusOfCommit(status: Status, sha: String, repo: String, completion: (status: Status?, error: NSError?) -> ()) { + private func _postStatusOfCommit(status: GitHubStatus, sha: String, repo: String, completion: (status: GitHubStatus?, error: NSError?) -> ()) { let params = [ "repo": repo, @@ -403,7 +470,7 @@ extension GitHubServer { ] let body = status.dictionarify() - self.sendRequestWithMethod(.POST, endpoint: .Statuses, params: params, query: nil, body: body) { (response, body, error) -> () in + self._sendRequestWithMethod(.POST, endpoint: .Statuses, params: params, query: nil, body: body) { (response, body, error) -> () in if error != nil { completion(status: nil, error: error) @@ -411,7 +478,7 @@ extension GitHubServer { } if let body = body as? NSDictionary { - let status = Status(json: body) + let status = GitHubStatus(json: body) completion(status: status, error: nil) } else { completion(status: nil, error: Error.withInfo("Wrong body \(body)")) @@ -424,14 +491,14 @@ extension GitHubServer { * and general issue comments - which appear in both Issues and Pull Requests (bc a PR is an Issue + code) * This API only fetches the general issue comments, NOT comments on code. */ - public func getCommentsOfIssue(issueNumber: Int, repo: String, completion: (comments: [Comment]?, error: NSError?) -> ()) { + private func _getCommentsOfIssue(issueNumber: Int, repo: String, completion: (comments: [GitHubComment]?, error: NSError?) -> ()) { let params = [ "repo": repo, "issue": issueNumber.description ] - self.sendRequestWithMethod(.GET, endpoint: .IssueComments, params: params, query: nil, body: nil) { (response, body, error) -> () in + self._sendRequestWithMethod(.GET, endpoint: .IssueComments, params: params, query: nil, body: nil) { (response, body, error) -> () in if error != nil { completion(comments: nil, error: error) @@ -439,7 +506,7 @@ extension GitHubServer { } if let body = body as? NSArray { - let comments: [Comment] = GitHubArray(body) + let comments: [GitHubComment] = GitHubArray(body) completion(comments: comments, error: nil) } else { completion(comments: nil, error: Error.withInfo("Wrong body \(body)")) @@ -450,7 +517,7 @@ extension GitHubServer { /** * POST a comment on an issue. */ - public func postCommentOnIssue(commentBody: String, issueNumber: Int, repo: String, completion: (comment: Comment?, error: NSError?) -> ()) { + private func _postCommentOnIssue(commentBody: String, issueNumber: Int, repo: String, completion: (comment: GitHubComment?, error: NSError?) -> ()) { let params = [ "repo": repo, @@ -461,7 +528,7 @@ extension GitHubServer { "body": commentBody ] - self.sendRequestWithMethod(.POST, endpoint: .IssueComments, params: params, query: nil, body: body) { (response, body, error) -> () in + self._sendRequestWithMethod(.POST, endpoint: .IssueComments, params: params, query: nil, body: body) { (response, body, error) -> () in if error != nil { completion(comment: nil, error: error) @@ -469,7 +536,7 @@ extension GitHubServer { } if let body = body as? NSDictionary { - let comment = Comment(json: body) + let comment = GitHubComment(json: body) completion(comment: comment, error: nil) } else { completion(comment: nil, error: Error.withInfo("Wrong body \(body)")) @@ -480,7 +547,7 @@ extension GitHubServer { /** * PATCH edit a comment with id */ - public func editCommentOnIssue(commentId: Int, newCommentBody: String, repo: String, completion: (comment: Comment?, error: NSError?) -> ()) { + private func _editCommentOnIssue(commentId: Int, newCommentBody: String, repo: String, completion: (comment: GitHubComment?, error: NSError?) -> ()) { let params = [ "repo": repo, @@ -491,7 +558,7 @@ extension GitHubServer { "body": newCommentBody ] - self.sendRequestWithMethod(.PATCH, endpoint: .IssueComments, params: params, query: nil, body: body) { (response, body, error) -> () in + self._sendRequestWithMethod(.PATCH, endpoint: .IssueComments, params: params, query: nil, body: body) { (response, body, error) -> () in if error != nil { completion(comment: nil, error: error) @@ -499,7 +566,7 @@ extension GitHubServer { } if let body = body as? NSDictionary { - let comment = Comment(json: body) + let comment = GitHubComment(json: body) completion(comment: comment, error: nil) } else { completion(comment: nil, error: Error.withInfo("Wrong body \(body)")) @@ -511,7 +578,7 @@ extension GitHubServer { * POST merge a head branch/commit into a base branch. * has a couple of different responses, a bit tricky */ - public func mergeHeadIntoBase(head head: String, base: String, commitMessage: String, repo: String, completion: (result: GitHubEndpoints.MergeResult?, error: NSError?) -> ()) { + private func _mergeHeadIntoBase(head head: String, base: String, commitMessage: String, repo: String, completion: (result: GitHubEndpoints.MergeResult?, error: NSError?) -> ()) { let params = [ "repo": repo @@ -523,7 +590,7 @@ extension GitHubServer { "commit_message": commitMessage ] - self.sendRequestWithMethod(.POST, endpoint: .Merges, params: params, query: nil, body: body) { (response, body, error) -> () in + self._sendRequestWithMethod(.POST, endpoint: .Merges, params: params, query: nil, body: body) { (response, body, error) -> () in if error != nil { completion(result: nil, error: error) @@ -562,13 +629,13 @@ extension GitHubServer { /** * GET branches of a repo */ - public func getBranchesOfRepo(repo: String, completion: (branches: [Branch]?, error: NSError?) -> ()) { + private func _getBranchesOfRepo(repo: String, completion: (branches: [GitHubBranch]?, error: NSError?) -> ()) { let params = [ "repo": repo ] - self.sendRequestWithMethod(.GET, endpoint: .Branches, params: params, query: nil, body: nil) { + self._sendRequestWithMethod(.GET, endpoint: .Branches, params: params, query: nil, body: nil) { (response, body, error) -> () in if error != nil { @@ -577,7 +644,7 @@ extension GitHubServer { } if let body = body as? NSArray { - let branches: [Branch] = GitHubArray(body) + let branches: [GitHubBranch] = GitHubArray(body) completion(branches: branches, error: nil) } else { completion(branches: nil, error: Error.withInfo("Wrong body \(body)")) @@ -588,13 +655,13 @@ extension GitHubServer { /** * GET repo metadata */ - public func getRepo(repo: String, completion: (repo: Repo?, error: NSError?) -> ()) { + private func _getRepo(repo: String, completion: (repo: GitHubRepo?, error: NSError?) -> ()) { let params = [ "repo": repo ] - self.sendRequestWithMethod(.GET, endpoint: .Repos, params: params, query: nil, body: nil) { + self._sendRequestWithMethod(.GET, endpoint: .Repos, params: params, query: nil, body: nil) { (response, body, error) -> () in if error != nil { @@ -603,7 +670,7 @@ extension GitHubServer { } if let body = body as? NSDictionary { - let repository: Repo = Repo(json: body) + let repository: GitHubRepo = GitHubRepo(json: body) completion(repo: repository, error: nil) } else { completion(repo: nil, error: Error.withInfo("Wrong body \(body)")) diff --git a/BuildaGitServer/GitHub/GitHubStatus.swift b/BuildaGitServer/GitHub/GitHubStatus.swift new file mode 100644 index 0000000..2cc4654 --- /dev/null +++ b/BuildaGitServer/GitHub/GitHubStatus.swift @@ -0,0 +1,105 @@ +// +// Status.swift +// Buildasaur +// +// Created by Honza Dvorsky on 13/12/2014. +// Copyright (c) 2014 Honza Dvorsky. All rights reserved. +// + +import Foundation +import BuildaUtils + +class GitHubStatus : GitHubEntity { + + enum GitHubState : String { + case NoState = "" + case Pending = "pending" + case Success = "success" + case Error = "error" + case Failure = "failure" + + static func fromBuildState(buildState: BuildState) -> GitHubState { + switch buildState { + case .NoState: + return .NoState + case .Pending: + return .Pending + case .Success: + return .Success + case .Error: + return .Error + case .Failure: + return .Failure + } + } + + func toBuildState() -> BuildState { + switch self { + case .NoState: + return .NoState + case .Pending: + return .Pending + case .Success: + return .Success + case .Error: + return .Error + case .Failure: + return .Failure + } + } + } + + let githubState: GitHubState + let description: String? + let targetUrl: String? + let context: String? + let created: String? + let creator: GitHubUser? + + required init(json: NSDictionary) { + + self.githubState = GitHubState(rawValue: json.stringForKey("state"))! + self.description = json.optionalStringForKey("description") + self.targetUrl = json.optionalStringForKey("target_url") + self.context = json.optionalStringForKey("context") + self.created = json.optionalStringForKey("created_at") + if let creator = json.optionalDictionaryForKey("creator") { + self.creator = GitHubUser(json: creator) + } else { + self.creator = nil + } + + super.init(json: json) + } + + init(state: GitHubState, description: String?, targetUrl: String?, context: String?) { + + self.githubState = state + self.description = description + self.targetUrl = targetUrl + self.context = context + self.creator = nil + self.created = nil + + super.init() + } + + override func dictionarify() -> NSDictionary { + + let dictionary = NSMutableDictionary() + + dictionary["state"] = self.githubState.rawValue + dictionary.optionallyAddValueForKey(self.description, key: "description") + dictionary.optionallyAddValueForKey(self.targetUrl, key: "target_url") + dictionary.optionallyAddValueForKey(self.context, key: "context") + + return dictionary + } +} + +extension GitHubStatus: StatusType { + + var state: BuildState { + return self.githubState.toBuildState() + } +} \ No newline at end of file diff --git a/BuildaGitServer/User.swift b/BuildaGitServer/GitHub/GitHubUser.swift similarity index 68% rename from BuildaGitServer/User.swift rename to BuildaGitServer/GitHub/GitHubUser.swift index 6704520..c9a06a1 100644 --- a/BuildaGitServer/User.swift +++ b/BuildaGitServer/GitHub/GitHubUser.swift @@ -8,13 +8,13 @@ import Foundation -public class User : GitHubEntity { +class GitHubUser : GitHubEntity { - public let userName: String - public let realName: String? - public let avatarUrl: String? + let userName: String + let realName: String? + let avatarUrl: String? - public required init(json: NSDictionary) { + required init(json: NSDictionary) { self.userName = json.stringForKey("login") self.realName = json.optionalStringForKey("name") diff --git a/BuildaGitServer/GitHubFactory.swift b/BuildaGitServer/GitHubFactory.swift deleted file mode 100644 index f13cf46..0000000 --- a/BuildaGitServer/GitHubFactory.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// GitHubFactory.swift -// Buildasaur -// -// Created by Honza Dvorsky on 13/12/2014. -// Copyright (c) 2014 Honza Dvorsky. All rights reserved. -// - -import Foundation - -public class GitHubFactory { - - public class func server(token: String?) -> GitHubServer { - - let baseURL = "https://api.github.com" - let endpoints = GitHubEndpoints(baseURL: baseURL, token: token) - - let server = GitHubServer(endpoints: endpoints) - return server - } - -} diff --git a/BuildaGitServer/GitServerPublic.swift b/BuildaGitServer/GitServerPublic.swift index 1c2b897..6ddd232 100644 --- a/BuildaGitServer/GitServerPublic.swift +++ b/BuildaGitServer/GitServerPublic.swift @@ -8,8 +8,75 @@ import Foundation import BuildaUtils +import Keys +import ReactiveCocoa + +public enum GitService: String { + case GitHub = "github" + case BitBucket = "bitbucket" +// case GitLab = "gitlab" + + public func prettyName() -> String { + switch self { + case .GitHub: return "GitHub" + case .BitBucket: return "BitBucket" + } + } + + public func logoName() -> String { + switch self { + case .GitHub: return "github" + case .BitBucket: return "bitbucket" + } + } + + public func hostname() -> String { + switch self { + case .GitHub: return "github.com" + case .BitBucket: return "bitbucket.org" + } + } + + public func authorizeUrl() -> String { + switch self { + case .GitHub: return "https://github.com/login/oauth/authorize" + case .BitBucket: return "https://bitbucket.org/site/oauth2/authorize" + } + } + + public func accessTokenUrl() -> String { + switch self { + case .GitHub: return "https://github.com/login/oauth/access_token" + case .BitBucket: return "https://bitbucket.org/site/oauth2/access_token" + } + } + + public func serviceKey() -> String { + switch self { + case .GitHub: return BuildasaurKeys().gitHubAPIClientId() + case .BitBucket: return BuildasaurKeys().bitBucketAPIClientId() + } + } + + public func serviceSecret() -> String { + switch self { + case .GitHub: return BuildasaurKeys().gitHubAPIClientSecret() + case .BitBucket: return BuildasaurKeys().bitBucketAPIClientSecret() + } + } +} public class GitServer : HTTPServer { + let service: GitService + + public func authChangedSignal() -> Signal { + return Signal.never + } + + init(service: GitService, http: HTTP? = nil) { + self.service = service + super.init(http: http) + } } diff --git a/BuildaGitServer/PullRequest.swift b/BuildaGitServer/PullRequest.swift deleted file mode 100644 index 2cea966..0000000 --- a/BuildaGitServer/PullRequest.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// PullRequest.swift -// Buildasaur -// -// Created by Honza Dvorsky on 13/12/2014. -// Copyright (c) 2014 Honza Dvorsky. All rights reserved. -// - -import Foundation - -public class PullRequest : Issue { - - public let head: PullRequestBranch - public let base: PullRequestBranch - - public required init(json: NSDictionary) { - - self.head = PullRequestBranch(json: json.dictionaryForKey("head")) - self.base = PullRequestBranch(json: json.dictionaryForKey("base")) - - super.init(json: json) - } -} - - diff --git a/BuildaGitServer/Repo.swift b/BuildaGitServer/Repo.swift deleted file mode 100644 index 0c39036..0000000 --- a/BuildaGitServer/Repo.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// Repo.swift -// Buildasaur -// -// Created by Honza Dvorsky on 13/12/2014. -// Copyright (c) 2014 Honza Dvorsky. All rights reserved. -// - -import Foundation - -public class Repo : GitHubEntity { - - public let name: String - public let fullName: String - public let repoUrlHTTPS: String - public let repoUrlSSH: String - public let permissions: NSDictionary - - public required init(json: NSDictionary) { - - self.name = json.stringForKey("name") - self.fullName = json.stringForKey("full_name") - self.repoUrlHTTPS = json.stringForKey("clone_url") - self.repoUrlSSH = json.stringForKey("ssh_url") - - if let permissions = json.optionalDictionaryForKey("permissions") { - self.permissions = permissions - } else { - self.permissions = NSDictionary() - } - - super.init(json: json) - } -} diff --git a/BuildaGitServer/Status.swift b/BuildaGitServer/Status.swift deleted file mode 100644 index 142459e..0000000 --- a/BuildaGitServer/Status.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// Status.swift -// Buildasaur -// -// Created by Honza Dvorsky on 13/12/2014. -// Copyright (c) 2014 Honza Dvorsky. All rights reserved. -// - -import Foundation -import BuildaUtils - -public class Status : GitHubEntity, Equatable { - - public enum State : String { - case NoState = "" - case Pending = "pending" - case Success = "success" - case Error = "error" - case Failure = "failure" - } - - public let state: State - public let description: String? - public let targetUrl: String? - public let context: String? - public let created: String? - public let creator: User? - - public required init(json: NSDictionary) { - - self.state = State(rawValue: json.stringForKey("state"))! - self.description = json.optionalStringForKey("description") - self.targetUrl = json.optionalStringForKey("target_url") - self.context = json.optionalStringForKey("context") - self.created = json.optionalStringForKey("created_at") - if let creator = json.optionalDictionaryForKey("creator") { - self.creator = User(json: creator) - } else { - self.creator = nil - } - - super.init(json: json) - } - - public init(state: State, description: String?, targetUrl: String?, context: String?) { - - self.state = state - self.description = description - self.targetUrl = targetUrl - self.context = context - self.creator = nil - self.created = nil - - super.init() - } - - public override func dictionarify() -> NSDictionary { - - let dictionary = NSMutableDictionary() - - dictionary["state"] = self.state.rawValue - dictionary.optionallyAddValueForKey(self.description, key: "description") - dictionary.optionallyAddValueForKey(self.targetUrl, key: "target_url") - dictionary.optionallyAddValueForKey(self.context, key: "context") - - return dictionary - } -} - -public func ==(lhs: Status, rhs: Status) -> Bool { - return lhs.state == rhs.state && lhs.description == rhs.description -} - -//for sending statuses upstream -extension Status { - - public class func toDict(state: State, description: String? = nil, targetUrl: String? = nil, context: String? = nil) -> [String: String] { - return [ - "state" : state.rawValue, - "target_url" : targetUrl ?? "", - "description" : description ?? "", - "context" : context ?? "" - ] - } - -} diff --git a/BuildaGitServerTests/BitBucketServerTests.swift b/BuildaGitServerTests/BitBucketServerTests.swift new file mode 100644 index 0000000..71391be --- /dev/null +++ b/BuildaGitServerTests/BitBucketServerTests.swift @@ -0,0 +1,184 @@ +// +// BitBucketServerTests.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/27/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Cocoa +import XCTest +@testable import BuildaGitServer +import BuildaUtils +import DVR +import Nimble + +class BitBucketServerTests: XCTestCase { + + var bitbucket: SourceServerType! + + override func setUp() { + super.setUp() + + } + + override func tearDown() { + + self.bitbucket = nil + + super.tearDown() + } + + func prepServerWithName(name: String) { + + let session = DVR.Session(cassetteName: name, testBundle: NSBundle(forClass: self.classForCoder)) + let http = HTTP(session: session) + self.bitbucket = GitServerFactory.server(.BitBucket, auth: nil, http: http) + } + + func testGetPullRequests() { + + self.prepServerWithName("bitbucket_get_prs") + + let exp = self.expectationWithDescription("Waiting for url request") + + self.bitbucket.getOpenPullRequests("honzadvorsky/buildasaur-tester") { (prs, error) -> () in + + expect(error).to(beNil()) + guard let prs = prs else { fail(); return } + + expect(prs.count) == 4 + + let pr = prs.first! + expect(pr.title) == "README.md edited online with Bitbucket" + expect(pr.number) == 4 + expect(pr.baseName) == "czechboy0-patch-6" + expect(pr.headCommitSHA) == "787ce956a784" + expect(pr.headName) == "honzadvorsky/readmemd-edited-online-with-bitbucket-1453476305123" + expect(pr.headRepo.originUrlSSH) == "git@bitbucket.org:honzadvorsky/buildasaur-tester.git" + + exp.fulfill() + } + + self.waitForExpectationsWithTimeout(10, handler: nil) + } + + func testGetPullRequest() { + + self.prepServerWithName("bitbucket_get_pr") + + let exp = self.expectationWithDescription("Waiting for url request") + + self.bitbucket.getPullRequest(4, repo: "honzadvorsky/buildasaur-tester") { (pr, error) -> () in + + expect(error).to(beNil()) + guard let pr = pr else { fail(); return } + + expect(pr.title) == "README.md edited online with Bitbucket" + expect(pr.number) == 4 + expect(pr.baseName) == "czechboy0-patch-6" + expect(pr.headCommitSHA) == "787ce956a784" + expect(pr.headName) == "honzadvorsky/readmemd-edited-online-with-bitbucket-1453476305123" + expect(pr.headRepo.originUrlSSH) == "git@bitbucket.org:honzadvorsky/buildasaur-tester.git" + + exp.fulfill() + } + + self.waitForExpectationsWithTimeout(10, handler: nil) + } + + func testGetRepo() { + + self.prepServerWithName("bitbucket_get_repo") + + let exp = self.expectationWithDescription("Waiting for url request") + + self.bitbucket.getRepo("honzadvorsky/buildasaur-tester") { (repo, error) -> () in + + expect(error).to(beNil()) + guard let repo = repo else { fail(); return } + + expect(repo.originUrlSSH) == "git@bitbucket.org:honzadvorsky/buildasaur-tester.git" + + exp.fulfill() + } + + self.waitForExpectationsWithTimeout(10, handler: nil) + } + + func testGetComments() { + + self.prepServerWithName("bitbucket_get_comments") + + let exp = self.expectationWithDescription("Waiting for url request") + + self.bitbucket.getCommentsOfIssue(4, repo: "honzadvorsky/buildasaur-tester") { (comments, error) -> () in + + expect(error).to(beNil()) + guard let comments: [CommentType] = comments else { fail(); return } + + expect(comments.count) == 2 + let c1 = comments[0].body + let c2 = comments[1].body + expect(c1) == "Another **hello world**" + expect(c2) == "Hello world" + + exp.fulfill() + } + + self.waitForExpectationsWithTimeout(10, handler: nil) + } + + func testPostStatus() { + + self.prepServerWithName("bitbucket_post_status") + + let exp = self.expectationWithDescription("Waiting for url request") + + let status = self.bitbucket.createStatusFromState(BuildState.Success, description: "All went great!", targetUrl: "https://stlt.herokuapp.com/v1/xcs_deeplink/honzadvysmbpr14.home/1413f8578e54c3d052b8121a250255c0/1413f8578e54c3d052b8121a2509a923") + + self.bitbucket.postStatusOfCommit("787ce95", status: status, repo: "honzadvorsky/buildasaur-tester") { (status, error) -> () in + + expect(error).to(beNil()) + guard let status = status else { fail(); return } + + expect(status.description) == "All went great!" + expect(status.state) == BuildState.Success + expect(status.targetUrl) == "https://stlt.herokuapp.com/v1/xcs_deeplink/honzadvysmbpr14.home/1413f8578e54c3d052b8121a250255c0/1413f8578e54c3d052b8121a2509a923" + + exp.fulfill() + } + + self.waitForExpectationsWithTimeout(10, handler: nil) + } + + func testGetStatus() { + + self.prepServerWithName("bitbucket_get_status") + + let exp = self.expectationWithDescription("Waiting for url request") + + self.bitbucket.getStatusOfCommit("787ce95", repo: "honzadvorsky/buildasaur-tester") { (status, error) -> () in + + expect(error).to(beNil()) + guard let status = status else { fail(); return } + + expect(status.description) == "All went great!" + expect(status.state) == BuildState.Success + expect(status.targetUrl) == "https://stlt.herokuapp.com/v1/xcs_deeplink/honzadvysmbpr14.home/1413f8578e54c3d052b8121a250255c0/1413f8578e54c3d052b8121a2509a923" + + exp.fulfill() + } + + self.waitForExpectationsWithTimeout(10, handler: nil) + } + + // func testPostComment() { + // //TODO: + // } + + // func testGetBranches() { + // //TODO: + // } + +} diff --git a/BuildaGitServerTests/Data/bitbucket_get_comments.json b/BuildaGitServerTests/Data/bitbucket_get_comments.json new file mode 100644 index 0000000..6c2e091 --- /dev/null +++ b/BuildaGitServerTests/Data/bitbucket_get_comments.json @@ -0,0 +1,142 @@ +{ + "interactions" : [ + { + "recorded_at" : 1453928110.383976, + "response" : { + "body" : { + "page" : 1, + "size" : 2, + "pagelen" : 10, + "values" : [ + { + "content" : { + "raw" : "Another **hello world**", + "markup" : "markdown", + "html" : "

Another hello world<\/strong><\/p>" + }, + "updated_on" : "2016-01-27T20:47:11.950649+00:00", + "id" : 13923541, + "pullrequest" : { + "id" : 4, + "links" : { + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/4" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4" + } + }, + "title" : "README.md edited online with Bitbucket", + "type" : "pullrequest" + }, + "links" : { + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/4\/_\/diff#comment-13923541" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/comments\/13923541" + } + }, + "created_on" : "2016-01-27T20:46:56.523167+00:00", + "user" : { + "username" : "honzadvorsky", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/account\/honzadvorsky\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/users\/honzadvorsky" + } + }, + "display_name" : "Honza Dvorsky", + "type" : "user", + "uuid" : "{c78fc04e-2594-4f79-a518-ae1593921663}" + } + }, + { + "content" : { + "raw" : "Hello world", + "markup" : "markdown", + "html" : "

Hello world<\/p>" + }, + "updated_on" : "2016-01-27T20:46:17.899168+00:00", + "id" : 13923520, + "pullrequest" : { + "id" : 4, + "links" : { + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/4" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4" + } + }, + "title" : "README.md edited online with Bitbucket", + "type" : "pullrequest" + }, + "links" : { + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/4\/_\/diff#comment-13923520" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/comments\/13923520" + } + }, + "created_on" : "2016-01-27T20:46:17.897343+00:00", + "user" : { + "username" : "honzadvorsky", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/account\/honzadvorsky\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/users\/honzadvorsky" + } + }, + "display_name" : "Honza Dvorsky", + "type" : "user", + "uuid" : "{c78fc04e-2594-4f79-a518-ae1593921663}" + } + } + ] + }, + "status" : 200, + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/comments", + "headers" : { + "Content-Type" : "application\/json; charset=utf-8", + "X-Render-Time" : "0.0898499488831", + "X-Frame-Options" : "SAMEORIGIN", + "X-Cache-Info" : "caching", + "Content-Encoding" : "gzip", + "Server" : "nginx\/1.6.2", + "X-Accepted-OAuth-Scopes" : "pullrequest", + "Transfer-Encoding" : "Identity", + "X-Request-Count" : "161", + "X-Version" : "66b40159990c", + "Cache-Control" : "max-age=900", + "Date" : "Wed, 27 Jan 2016 20:55:10 GMT", + "Strict-Transport-Security" : "max-age=31536000", + "X-Static-Version" : "66b40159990c", + "Connection" : "keep-alive", + "X-Content-Type-Options" : "nosniff", + "X-Served-By" : "app19", + "Vary" : "Authorization, Cookie, Accept-Encoding" + } + }, + "request" : { + "method" : "GET", + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/comments", + "headers" : { + + } + } + } + ], + "name" : "bitbucket_get_comments" +} diff --git a/BuildaGitServerTests/Data/bitbucket_get_pr.json b/BuildaGitServerTests/Data/bitbucket_get_pr.json new file mode 100644 index 0000000..6d305f7 --- /dev/null +++ b/BuildaGitServerTests/Data/bitbucket_get_pr.json @@ -0,0 +1,166 @@ +{ + "interactions" : [ + { + "recorded_at" : 1453927044.153671, + "response" : { + "body" : { + "description" : "README.md edited online with Bitbucket", + "state" : "OPEN", + "comment_count" : 0, + "id" : 4, + "reviewers" : [ + + ], + "author" : { + "username" : "honzadvorsky", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/account\/honzadvorsky\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/users\/honzadvorsky" + } + }, + "display_name" : "Honza Dvorsky", + "type" : "user", + "uuid" : "{c78fc04e-2594-4f79-a518-ae1593921663}" + }, + "merge_commit" : null, + "close_source_branch" : true, + "closed_by" : null, + "source" : { + "branch" : { + "name" : "honzadvorsky\/readmemd-edited-online-with-bitbucket-1453476305123" + }, + "commit" : { + "hash" : "787ce956a784", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce956a784" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "title" : "README.md edited online with Bitbucket", + "created_on" : "2016-01-22T15:25:09.703811+00:00", + "participants" : [ + + ], + "type" : "pullrequest", + "links" : { + "comments" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/comments" + }, + "decline" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/decline" + }, + "commits" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/commits" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/4" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4" + }, + "merge" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/merge" + }, + "activity" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/activity" + }, + "diff" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/diff" + }, + "approve" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/approve" + } + }, + "reason" : "", + "destination" : { + "branch" : { + "name" : "czechboy0-patch-6" + }, + "commit" : { + "hash" : "eeac335d8077", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/eeac335d8077" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "task_count" : 0, + "updated_on" : "2016-01-22T15:29:04.285575+00:00" + }, + "status" : 200, + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4", + "headers" : { + "Content-Type" : "application\/json; charset=utf-8", + "X-Render-Time" : "0.0324521064758", + "X-Frame-Options" : "SAMEORIGIN", + "X-Cache-Info" : "caching", + "Content-Encoding" : "gzip", + "Server" : "nginx\/1.6.2", + "X-Accepted-OAuth-Scopes" : "pullrequest", + "Transfer-Encoding" : "Identity", + "X-Request-Count" : "471", + "X-Version" : "66b40159990c", + "Cache-Control" : "max-age=900", + "Date" : "Wed, 27 Jan 2016 20:37:24 GMT", + "Strict-Transport-Security" : "max-age=31536000", + "X-Static-Version" : "66b40159990c", + "Connection" : "keep-alive", + "X-Content-Type-Options" : "nosniff", + "X-Served-By" : "app22", + "Vary" : "Authorization, Cookie, Accept-Encoding" + } + }, + "request" : { + "method" : "GET", + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4", + "headers" : { + + } + } + } + ], + "name" : "bitbucket_get_pr" +} diff --git a/BuildaGitServerTests/Data/bitbucket_get_prs.json b/BuildaGitServerTests/Data/bitbucket_get_prs.json new file mode 100644 index 0000000..a0909d1 --- /dev/null +++ b/BuildaGitServerTests/Data/bitbucket_get_prs.json @@ -0,0 +1,522 @@ +{ + "interactions" : [ + { + "recorded_at" : 1453916722.263993, + "response" : { + "body" : { + "page" : 1, + "size" : 4, + "pagelen" : 10, + "values" : [ + { + "description" : "README.md edited online with Bitbucket", + "state" : "OPEN", + "id" : 4, + "author" : { + "username" : "honzadvorsky", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/account\/honzadvorsky\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/users\/honzadvorsky" + } + }, + "display_name" : "Honza Dvorsky", + "type" : "user", + "uuid" : "{c78fc04e-2594-4f79-a518-ae1593921663}" + }, + "merge_commit" : null, + "close_source_branch" : true, + "closed_by" : null, + "source" : { + "branch" : { + "name" : "honzadvorsky\/readmemd-edited-online-with-bitbucket-1453476305123" + }, + "commit" : { + "hash" : "787ce956a784", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce956a784" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "title" : "README.md edited online with Bitbucket", + "created_on" : "2016-01-22T15:25:09.703811+00:00", + "type" : "pullrequest", + "links" : { + "comments" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/comments" + }, + "decline" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/decline" + }, + "commits" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/commits" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/4" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4" + }, + "merge" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/merge" + }, + "activity" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/activity" + }, + "diff" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/diff" + }, + "approve" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/4\/approve" + } + }, + "reason" : "", + "destination" : { + "branch" : { + "name" : "czechboy0-patch-6" + }, + "commit" : { + "hash" : "eeac335d8077", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/eeac335d8077" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "updated_on" : "2016-01-22T15:29:04.285575+00:00" + }, + { + "description" : "", + "state" : "OPEN", + "id" : 3, + "author" : { + "username" : "honzadvorsky", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/account\/honzadvorsky\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/users\/honzadvorsky" + } + }, + "display_name" : "Honza Dvorsky", + "type" : "user", + "uuid" : "{c78fc04e-2594-4f79-a518-ae1593921663}" + }, + "merge_commit" : null, + "close_source_branch" : false, + "closed_by" : null, + "source" : { + "branch" : { + "name" : "czechboy0-patch-4" + }, + "commit" : { + "hash" : "b4ff0bf1a6fd", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/b4ff0bf1a6fd" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "title" : "Update ViewController.swift (build failing)", + "created_on" : "2016-01-21T19:37:39.919512+00:00", + "type" : "pullrequest", + "links" : { + "comments" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/3\/comments" + }, + "decline" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/3\/decline" + }, + "commits" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/3\/commits" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/3" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/3" + }, + "merge" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/3\/merge" + }, + "activity" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/3\/activity" + }, + "diff" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/3\/diff" + }, + "approve" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/3\/approve" + } + }, + "reason" : "", + "destination" : { + "branch" : { + "name" : "master" + }, + "commit" : { + "hash" : "166403db74c5", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/166403db74c5" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "updated_on" : "2016-01-21T19:56:33.372763+00:00" + }, + { + "description" : "* device-specific test failures\r\n\r\n* added a warning", + "state" : "OPEN", + "id" : 2, + "author" : { + "username" : "honzadvorsky", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/account\/honzadvorsky\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/users\/honzadvorsky" + } + }, + "display_name" : "Honza Dvorsky", + "type" : "user", + "uuid" : "{c78fc04e-2594-4f79-a518-ae1593921663}" + }, + "merge_commit" : null, + "close_source_branch" : false, + "closed_by" : null, + "source" : { + "branch" : { + "name" : "hd\/test_fail_diff_devices" + }, + "commit" : { + "hash" : "bc7b75eefb60", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/bc7b75eefb60" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "title" : "device-specific test failures (tests failing)", + "created_on" : "2016-01-21T19:37:15.597471+00:00", + "type" : "pullrequest", + "links" : { + "comments" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/2\/comments" + }, + "decline" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/2\/decline" + }, + "commits" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/2\/commits" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/2" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/2" + }, + "merge" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/2\/merge" + }, + "activity" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/2\/activity" + }, + "diff" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/2\/diff" + }, + "approve" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/2\/approve" + } + }, + "reason" : "", + "destination" : { + "branch" : { + "name" : "master" + }, + "commit" : { + "hash" : "166403db74c5", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/166403db74c5" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "updated_on" : "2016-01-21T19:37:15.617639+00:00" + }, + { + "description" : "hello world description\r\nand \r\n\r\nmore\r\nhere", + "state" : "OPEN", + "id" : 1, + "author" : { + "username" : "honzadvorsky", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/account\/honzadvorsky\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/users\/honzadvorsky" + } + }, + "display_name" : "Honza Dvorsky", + "type" : "user", + "uuid" : "{c78fc04e-2594-4f79-a518-ae1593921663}" + }, + "merge_commit" : null, + "close_source_branch" : false, + "closed_by" : null, + "source" : { + "branch" : { + "name" : "czechboy0-patch-6" + }, + "commit" : { + "hash" : "eeac335d8077", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/eeac335d8077" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "title" : "test change (test failing)", + "created_on" : "2016-01-21T19:36:46.519307+00:00", + "type" : "pullrequest", + "links" : { + "comments" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/1\/comments" + }, + "decline" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/1\/decline" + }, + "commits" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/1\/commits" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/pull-requests\/1" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/1" + }, + "merge" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/1\/merge" + }, + "activity" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/1\/activity" + }, + "diff" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/1\/diff" + }, + "approve" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests\/1\/approve" + } + }, + "reason" : "", + "destination" : { + "branch" : { + "name" : "master" + }, + "commit" : { + "hash" : "166403db74c5", + "links" : { + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/166403db74c5" + } + } + }, + "repository" : { + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + } + }, + "full_name" : "honzadvorsky\/buildasaur-tester", + "type" : "repository", + "name" : "Buildasaur-Tester", + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}" + } + }, + "updated_on" : "2016-01-21T19:36:46.537745+00:00" + } + ] + }, + "status" : 200, + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests", + "headers" : { + "Content-Type" : "application\/json; charset=utf-8", + "X-Render-Time" : "0.143883943558", + "X-Frame-Options" : "SAMEORIGIN", + "X-Cache-Info" : "cached", + "Content-Encoding" : "gzip", + "Server" : "nginx\/1.6.2", + "X-Accepted-OAuth-Scopes" : "pullrequest", + "Transfer-Encoding" : "Identity", + "X-Request-Count" : "28", + "X-Version" : "66b40159990c", + "Cache-Control" : "max-age=900", + "Date" : "Wed, 27 Jan 2016 17:41:05 GMT", + "Strict-Transport-Security" : "max-age=31536000", + "X-Static-Version" : "66b40159990c", + "Connection" : "Keep-Alive", + "X-Content-Type-Options" : "nosniff", + "X-Served-By" : "app18", + "Vary" : "Authorization, Cookie, Accept-Encoding" + } + }, + "request" : { + "method" : "GET", + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests", + "headers" : { + + } + } + } + ], + "name" : "bitbucket_get_prs" +} diff --git a/BuildaGitServerTests/Data/bitbucket_get_repo.json b/BuildaGitServerTests/Data/bitbucket_get_repo.json new file mode 100644 index 0000000..fedff78 --- /dev/null +++ b/BuildaGitServerTests/Data/bitbucket_get_repo.json @@ -0,0 +1,112 @@ +{ + "interactions" : [ + { + "recorded_at" : 1453927330.056467, + "response" : { + "body" : { + "uuid" : "{c839d4b0-eeb6-4e09-865e-f29db3e51f28}", + "full_name" : "honzadvorsky\/buildasaur-tester", + "description" : "", + "owner" : { + "username" : "honzadvorsky", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/account\/honzadvorsky\/avatar\/32\/" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/users\/honzadvorsky" + } + }, + "display_name" : "Honza Dvorsky", + "type" : "user", + "uuid" : "{c78fc04e-2594-4f79-a518-ae1593921663}" + }, + "type" : "repository", + "size" : 163544, + "created_on" : "2016-01-21T19:29:27.826816+00:00", + "has_wiki" : false, + "fork_policy" : "allow_forks", + "links" : { + "avatar" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester\/avatar\/32\/" + }, + "forks" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/forks" + }, + "pullrequests" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/pullrequests" + }, + "html" : { + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester" + }, + "commits" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commits" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester" + }, + "hooks" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/hooks" + }, + "downloads" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/downloads" + }, + "clone" : [ + { + "name" : "https", + "href" : "https:\/\/bitbucket.org\/honzadvorsky\/buildasaur-tester.git" + }, + { + "name" : "ssh", + "href" : "ssh:\/\/git@bitbucket.org\/honzadvorsky\/buildasaur-tester.git" + } + ], + "watchers" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/watchers" + } + }, + "language" : "swift", + "scm" : "git", + "has_issues" : false, + "website" : "", + "is_private" : false, + "name" : "Buildasaur-Tester", + "updated_on" : "2016-01-27T17:08:54.773844+00:00" + }, + "status" : 200, + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester", + "headers" : { + "Content-Type" : "application\/json; charset=utf-8", + "X-Render-Time" : "0.0204379558563", + "X-Frame-Options" : "SAMEORIGIN", + "X-Cache-Info" : "caching", + "Content-Encoding" : "gzip", + "Server" : "nginx\/1.6.2", + "X-Accepted-OAuth-Scopes" : "repository", + "Transfer-Encoding" : "Identity", + "X-Request-Count" : "471", + "X-Version" : "66b40159990c", + "Cache-Control" : "max-age=900", + "Date" : "Wed, 27 Jan 2016 20:42:09 GMT", + "Strict-Transport-Security" : "max-age=31536000", + "X-Static-Version" : "66b40159990c", + "Connection" : "keep-alive", + "X-Content-Type-Options" : "nosniff", + "X-Served-By" : "app22", + "Vary" : "Authorization, Cookie, Accept-Encoding" + } + }, + "request" : { + "method" : "GET", + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester", + "headers" : { + + } + } + } + ], + "name" : "bitbucket_get_repo" +} diff --git a/BuildaGitServerTests/Data/bitbucket_get_status.json b/BuildaGitServerTests/Data/bitbucket_get_status.json new file mode 100644 index 0000000..0ad8c10 --- /dev/null +++ b/BuildaGitServerTests/Data/bitbucket_get_status.json @@ -0,0 +1,56 @@ +{ + "interactions" : [ + { + "recorded_at" : 1453932419.386528, + "response" : { + "body" : { + "state" : "SUCCESSFUL", + "updated_on" : "2016-01-27T22:00:48.330475+00:00", + "description" : "All went great!", + "links" : { + "commit" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce956a784346755ac1a01da7bc1e20f87c006" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce956a784346755ac1a01da7bc1e20f87c006\/statuses\/build\/Buildasaur" + } + }, + "key" : "Buildasaur", + "created_on" : "2016-01-27T21:52:34.066284+00:00", + "type" : "build", + "name" : "Buildasaur", + "url" : "https:\/\/stlt.herokuapp.com\/v1\/xcs_deeplink\/honzadvysmbpr14.home\/1413f8578e54c3d052b8121a250255c0\/1413f8578e54c3d052b8121a2509a923" + }, + "status" : 200, + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce95\/statuses\/build\/Buildasaur", + "headers" : { + "Content-Type" : "application\/json; charset=utf-8", + "X-Render-Time" : "0.498512983322", + "X-Frame-Options" : "SAMEORIGIN", + "Content-Encoding" : "gzip", + "Server" : "nginx\/1.6.2", + "X-Accepted-OAuth-Scopes" : "repository", + "Transfer-Encoding" : "Identity", + "X-Request-Count" : "69", + "X-OAuth-Scopes" : "pullrequest:write", + "X-Version" : "66b40159990c", + "Date" : "Wed, 27 Jan 2016 22:06:59 GMT", + "Strict-Transport-Security" : "max-age=31536000", + "X-Static-Version" : "66b40159990c", + "Connection" : "keep-alive", + "X-Content-Type-Options" : "nosniff", + "X-Served-By" : "app18", + "Vary" : "Authorization, Cookie" + } + }, + "request" : { + "method" : "GET", + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce95\/statuses\/build\/Buildasaur", + "headers" : { + "Authorization" : "Bearer dont_looky_here" + } + } + } + ], + "name" : "bitbucket_get_status" +} diff --git a/BuildaGitServerTests/Data/bitbucket_post_status.json b/BuildaGitServerTests/Data/bitbucket_post_status.json new file mode 100644 index 0000000..ba82498 --- /dev/null +++ b/BuildaGitServerTests/Data/bitbucket_post_status.json @@ -0,0 +1,64 @@ +{ + "interactions" : [ + { + "recorded_at" : 1453932048.4355, + "response" : { + "body" : { + "state" : "SUCCESSFUL", + "updated_on" : "2016-01-27T22:00:48.330475+00:00", + "description" : "All went great!", + "links" : { + "commit" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce956a784346755ac1a01da7bc1e20f87c006" + }, + "self" : { + "href" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce956a784346755ac1a01da7bc1e20f87c006\/statuses\/build\/Buildasaur" + } + }, + "key" : "Buildasaur", + "created_on" : "2016-01-27T21:52:34.066284+00:00", + "type" : "build", + "name" : "Buildasaur", + "url" : "https:\/\/stlt.herokuapp.com\/v1\/xcs_deeplink\/honzadvysmbpr14.home\/1413f8578e54c3d052b8121a250255c0\/1413f8578e54c3d052b8121a2509a923" + }, + "status" : 200, + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce95\/statuses\/build", + "headers" : { + "Content-Type" : "application\/json", + "X-Render-Time" : "0.0719389915466", + "X-Frame-Options" : "SAMEORIGIN", + "Content-Encoding" : "gzip", + "Server" : "nginx\/1.6.2", + "X-Accepted-OAuth-Scopes" : "repository", + "Transfer-Encoding" : "Identity", + "X-Request-Count" : "494", + "X-OAuth-Scopes" : "pullrequest:write", + "X-Version" : "66b40159990c", + "Date" : "Wed, 27 Jan 2016 22:00:48 GMT", + "Strict-Transport-Security" : "max-age=31536000", + "X-Static-Version" : "66b40159990c", + "Connection" : "keep-alive", + "X-Content-Type-Options" : "nosniff", + "X-Served-By" : "app19", + "Vary" : "Authorization, Cookie" + } + }, + "request" : { + "method" : "POST", + "body" : { + "state" : "SUCCESSFUL", + "key" : "Buildasaur", + "description" : "All went great!", + "name" : "Buildasaur", + "url" : "https:\/\/stlt.herokuapp.com\/v1\/xcs_deeplink\/honzadvysmbpr14.home\/1413f8578e54c3d052b8121a250255c0\/1413f8578e54c3d052b8121a2509a923" + }, + "url" : "https:\/\/api.bitbucket.org\/2.0\/repositories\/honzadvorsky\/buildasaur-tester\/commit\/787ce95\/statuses\/build", + "headers" : { + "Authorization" : "Bearer abcd_dont_loook_here", + "Content-Type" : "application\/json; charset=utf-8" + } + } + } + ], + "name" : "bitbucket_post_status" +} diff --git a/BuildaGitServerTests/GitHubServerTests.swift b/BuildaGitServerTests/GitHubServerTests.swift index 1799dd9..711688f 100644 --- a/BuildaGitServerTests/GitHubServerTests.swift +++ b/BuildaGitServerTests/GitHubServerTests.swift @@ -8,7 +8,7 @@ import Cocoa import XCTest -import BuildaGitServer +@testable import BuildaGitServer import BuildaUtils class GitHubSourceTests: XCTestCase { @@ -18,7 +18,7 @@ class GitHubSourceTests: XCTestCase { override func setUp() { super.setUp() - self.github = GitHubFactory.server(nil) + self.github = GitServerFactory.server(.GitHub, auth: nil) as! GitHubServer } override func tearDown() { @@ -53,7 +53,7 @@ class GitHubSourceTests: XCTestCase { XCTAssertNotNil(body, "Body must be non-nil") if let body = body as? NSArray { - let prs: [PullRequest] = GitHubArray(body) + let prs: [GitHubPullRequest] = GitHubArray(body) XCTAssertGreaterThan(prs.count, 0, "We need > 0 items to test parsing") Log.verbose("Parsed PRs: \(prs)") } else { @@ -72,7 +72,7 @@ class GitHubSourceTests: XCTestCase { XCTAssertNotNil(body, "Body must be non-nil") if let body = body as? NSArray { - let branches: [Branch] = GitHubArray(body) + let branches: [GitHubBranch] = GitHubArray(body) XCTAssertGreaterThan(branches.count, 0, "We need > 0 items to test parsing") Log.verbose("Parsed branches: \(branches)") } else { @@ -92,7 +92,7 @@ class GitHubSourceTests: XCTestCase { "html_url": "https://github.com/czechboy0" ] - let user = User(json: dictionary) + let user = GitHubUser(json: dictionary) XCTAssertEqual(user.userName, "czechboy0") XCTAssertEqual(user.realName!, "Honza Dvorsky") XCTAssertEqual(user.avatarUrl!, "https://avatars.githubusercontent.com/u/2182121?v=3") @@ -109,7 +109,7 @@ class GitHubSourceTests: XCTestCase { "html_url": "https://github.com/czechboy0/Buildasaur" ] - let repo = Repo(json: dictionary) + let repo = GitHubRepo(json: dictionary) XCTAssertEqual(repo.name, "Buildasaur") XCTAssertEqual(repo.fullName, "czechboy0/Buildasaur") XCTAssertEqual(repo.repoUrlHTTPS, "https://github.com/czechboy0/Buildasaur.git") @@ -124,7 +124,7 @@ class GitHubSourceTests: XCTestCase { "url": "https://api.github.com/repos/czechboy0/Buildasaur/commits/08182438ed2ef3b34bd97db85f39deb60e2dcd7d" ] - let commit = Commit(json: dictionary) + let commit = GitHubCommit(json: dictionary) XCTAssertEqual(commit.sha, "08182438ed2ef3b34bd97db85f39deb60e2dcd7d") XCTAssertEqual(commit.url!, "https://api.github.com/repos/czechboy0/Buildasaur/commits/08182438ed2ef3b34bd97db85f39deb60e2dcd7d") } @@ -140,7 +140,7 @@ class GitHubSourceTests: XCTestCase { "commit": commitDictionary ] - let branch = Branch(json: dictionary) + let branch = GitHubBranch(json: dictionary) XCTAssertEqual(branch.name, "master") XCTAssertEqual(branch.commit.sha, "08182438ed2ef3b34bd97db85f39deb60e2dcd7d") XCTAssertEqual(branch.commit.url!, "https://api.github.com/repos/czechboy0/Buildasaur/commits/08182438ed2ef3b34bd97db85f39deb60e2dcd7d") @@ -174,7 +174,7 @@ class GitHubSourceTests: XCTestCase { ] ] - let prbranch = PullRequestBranch(json: dictionary) + let prbranch = GitHubPullRequestBranch(json: dictionary) XCTAssertEqual(prbranch.ref, "fb-loadNode") XCTAssertEqual(prbranch.sha, "7e45fa772565969ee801b0bdce0f560122e34610") XCTAssertEqual(prbranch.repo.name, "AsyncDisplayKit") diff --git a/BuildaKit/Availability.swift b/BuildaKit/Availability.swift index 2156fd3..1c4faf0 100644 --- a/BuildaKit/Availability.swift +++ b/BuildaKit/Availability.swift @@ -52,7 +52,7 @@ public class AvailabilityChecker { return } - NetworkUtils.checkAvailabilityOfGitHubWithCurrentSettingsOfProject(project, completion: { (success, error) -> () in + NetworkUtils.checkAvailabilityOfServiceWithProject(project, completion: { (success, error) -> () in NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in diff --git a/BuildaKit/GitRepoMetadataParser.swift b/BuildaKit/GitRepoMetadataParser.swift index 405e583..ffe9dca 100644 --- a/BuildaKit/GitRepoMetadataParser.swift +++ b/BuildaKit/GitRepoMetadataParser.swift @@ -22,7 +22,14 @@ class GitRepoMetadataParser: SourceControlFileParser { //find the first origin ending with "(fetch)" let remotes = try run("git remote -v") - let fetchRemote = remotes.split("\n").filter { $0.hasSuffix("(fetch)") }.first + let fetchRemotes = remotes.split("\n").filter { $0.hasSuffix("(fetch)") } + var fetchRemote = fetchRemotes.first + if fetchRemotes.count > 1 { + //choose the one named "origin" if it exists, best guess + if let origin = fetchRemotes.filter({ $0.hasPrefix("origin") }).first { + fetchRemote = origin + } + } guard let remoteLine = fetchRemote else { throw Error.withInfo("No fetch remote found in \(remotes)") } diff --git a/BuildaKit/Logging.swift b/BuildaKit/Logging.swift index 4ec0801..c179aff 100644 --- a/BuildaKit/Logging.swift +++ b/BuildaKit/Logging.swift @@ -13,7 +13,9 @@ public class Logging { public class func setup(persistence: Persistence, alsoIntoFile: Bool) { - let path = persistence.fileURLWithName("Builda.log", intention: .Writing, isDirectory: false) + let path = persistence + .fileURLWithName("Logs", intention: .Writing, isDirectory: true) + .URLByAppendingPathComponent("Builda.log", isDirectory: false) var loggers = [Logger]() diff --git a/BuildaKit/NetworkUtils.swift b/BuildaKit/NetworkUtils.swift index 198b8b2..65b7cc0 100644 --- a/BuildaKit/NetworkUtils.swift +++ b/BuildaKit/NetworkUtils.swift @@ -13,57 +13,72 @@ import XcodeServerSDK public class NetworkUtils { - public class func checkAvailabilityOfGitHubWithCurrentSettingsOfProject(project: Project, completion: (success: Bool, error: NSError?) -> ()) { - - let token = project.config.value.githubToken - let server = GitHubFactory.server(token) - let credentialValidationBlueprint = project.createSourceControlBlueprintForCredentialVerification() + public typealias VerificationCompletion = (success: Bool, error: ErrorType?) -> () + + public class func checkAvailabilityOfServiceWithProject(project: Project, completion: VerificationCompletion) { - //check if we can get PRs, that should be representative enough - if let repoName = project.githubRepoName() { + self.checkService(project, completion: { success, error in - //we have a repo name - server.getRepo(repoName, completion: { (repo, error) -> () in - - if error != nil { - completion(success: false, error: error) - return - } + if !success { + completion(success: false, error: error) + return + } + + //now test ssh keys + let credentialValidationBlueprint = project.createSourceControlBlueprintForCredentialVerification() + self.checkValidityOfSSHKeys(credentialValidationBlueprint, completion: { (success, error) -> () in - if - let repo = repo, - let readPermission = repo.permissions["pull"] as? Bool, - let writePermission = repo.permissions["push"] as? Bool - { - - //look at the permissions in the PR metadata - if !readPermission { - completion(success: false, error: Error.withInfo("Missing read permission for repo")) - } else if !writePermission { - completion(success: false, error: Error.withInfo("Missing write permission for repo")) - } else { - //now test ssh keys - //TODO: do SSH Key validation properly in the new UI once we have Xcode Server credentials. - self.checkValidityOfSSHKeys(credentialValidationBlueprint, completion: { (success, error) -> () in - - if success { - Log.verbose("Finished blueprint validation with success!") - } else { - Log.verbose("Finished blueprint validation with error: \(error)") - } - - //now complete - completion(success: success, error: error) - }) - } + if success { + Log.verbose("Finished blueprint validation with success!") } else { - completion(success: false, error: Error.withInfo("Couldn't find repo permissions in GitHub response")) + Log.verbose("Finished blueprint validation with error: \(error)") } + + //now complete + completion(success: success, error: error) }) - - } else { + }) + } + + private class func checkService(project: Project, completion: VerificationCompletion) { + + let auth = project.config.value.serverAuthentication + let service = auth!.service + let server = SourceServerFactory().createServer(service, auth: auth) + + //check if we can get the repo and verify permissions + guard let repoName = project.serviceRepoName() else { completion(success: false, error: Error.withInfo("Invalid repo name")) + return } + + //we have a repo name + server.getRepo(repoName, completion: { (repo, error) -> () in + + if error != nil { + completion(success: false, error: error) + return + } + + if let repo = repo { + + let permissions = repo.permissions + let readPermission = permissions.read + let writePermission = permissions.write + + //look at the permissions in the PR metadata + if !readPermission { + completion(success: false, error: Error.withInfo("Missing read permission for repo")) + } else if !writePermission { + completion(success: false, error: Error.withInfo("Missing write permission for repo")) + } else { + //now complete + completion(success: true, error: nil) + } + } else { + completion(success: false, error: Error.withInfo("Couldn't find repo permissions in \(service.prettyName()) response")) + } + }) } public class func checkAvailabilityOfXcodeServerWithCurrentSettings(config: XcodeServerConfig, completion: (success: Bool, error: NSError?) -> ()) { diff --git a/BuildaKit/Persistence.swift b/BuildaKit/Persistence.swift index 50dfbf6..87222d6 100644 --- a/BuildaKit/Persistence.swift +++ b/BuildaKit/Persistence.swift @@ -228,6 +228,16 @@ public class Persistence { _ = try? self.fileManager.copyItemAtURL(url, toURL: writeUrl) } + public func copyFileToFolder(fileName: String, folder: String) { + + let url = self.fileURLWithName(fileName, intention: .Reading, isDirectory: false) + let writeUrl = self + .fileURLWithName(folder, intention: .Writing, isDirectory: true) + .URLByAppendingPathComponent(fileName, isDirectory: false) + + _ = try? self.fileManager.copyItemAtURL(url, toURL: writeUrl) + } + public func createFolderIfNotExists(url: NSURL) { let fm = self.fileManager @@ -244,7 +254,7 @@ public class Persistence { case WritingNoCreateFolder } - private func folderForIntention(intention: PersistenceIntention) -> NSURL { + func folderForIntention(intention: PersistenceIntention) -> NSURL { switch intention { case .Reading: return self.readingFolder diff --git a/BuildaKit/PersistenceMigrator.swift b/BuildaKit/PersistenceMigrator.swift index 7d8906e..25203eb 100644 --- a/BuildaKit/PersistenceMigrator.swift +++ b/BuildaKit/PersistenceMigrator.swift @@ -8,6 +8,8 @@ import Foundation import BuildaUtils +import XcodeServerSDK +import BuildaGitServer public protocol MigratorType { init(persistence: Persistence) @@ -47,7 +49,8 @@ public class CompositeMigrator: MigratorType { public required init(persistence: Persistence) { self.childMigrators = [ Migrator_v0_v1(persistence: persistence), - Migrator_v1_v2(persistence: persistence) + Migrator_v1_v2(persistence: persistence), + Migrator_v2_v3(persistence: persistence) ] } @@ -56,7 +59,9 @@ public class CompositeMigrator: MigratorType { } public func attemptMigration() throws { - try self.childMigrators.forEach { try $0.attemptMigration() } + try self.childMigrators + .filter { $0.isMigrationRequired() } + .forEach { try $0.attemptMigration() } } } @@ -107,8 +112,6 @@ class Migrator_v0_v1: MigratorType { /* - ServerConfigs.json: each server now has an id - - - Config.json: persistence_version: 1 -> 2 */ class Migrator_v1_v2: MigratorType { @@ -271,3 +274,115 @@ class Migrator_v1_v2: MigratorType { self.persistence.saveDictionary("Config.json", item: mutableConfig) } } + +/* +- ServerConfigs.json: password moved to the keychain +- Projects.json: github_token -> oauth_tokens keychain, ssh_passphrase moved to keychain +- move any .log files to a separate folder called 'Logs' +- "token1234" -> "github:username:personaltoken:token1234" +*/ +class Migrator_v2_v3: MigratorType { + + internal var persistence: Persistence + required init(persistence: Persistence) { + self.persistence = persistence + } + + func isMigrationRequired() -> Bool { + + return self.persistenceVersion() == 2 + } + + func attemptMigration() throws { + + let pers = self.persistence + + //migrate + self.migrateProjectAuthentication() + self.migrateServerAuthentication() + self.migrateLogs() + + //copy the rest + pers.copyFileToWriteLocation("Syncers.json", isDirectory: false) + pers.copyFileToWriteLocation("BuildTemplates", isDirectory: true) + pers.copyFileToWriteLocation("Triggers", isDirectory: true) + + let config = self.config() + let mutableConfig = config.mutableCopy() as! NSMutableDictionary + mutableConfig[kPersistenceVersion] = 3 + + //save the updated config + pers.saveDictionary("Config.json", item: mutableConfig) + } + + func migrateProjectAuthentication() { + + let pers = self.persistence + let projects = pers.loadArrayOfDictionariesFromFile("Projects.json") ?? [] + let mutableProjects = projects.map { $0.mutableCopy() as! NSMutableDictionary } + + let renamedAuth = mutableProjects.map { + (d: NSMutableDictionary) -> NSDictionary in + + let id = d.stringForKey("id") + let token = d.stringForKey("github_token") + let auth = ProjectAuthenticator(service: .GitHub, username: "GIT", type: .PersonalToken, secret: token) + let formattedToken = auth.toString() + + let passphrase = d.optionalStringForKey("ssh_passphrase") + d.removeObjectForKey("github_token") + d.removeObjectForKey("ssh_passphrase") + + let tokenKeychain = SecurePersistence.sourceServerTokenKeychain() + tokenKeychain.writeIfNeeded(id, value: formattedToken) + + let passphraseKeychain = SecurePersistence.sourceServerPassphraseKeychain() + passphraseKeychain.writeIfNeeded(id, value: passphrase) + + precondition(tokenKeychain.read(id) == formattedToken, "Saved token must match") + precondition(passphraseKeychain.read(id) == passphrase, "Saved passphrase must match") + + return d + } + + pers.saveArray("Projects.json", items: renamedAuth) + } + + func migrateServerAuthentication() { + + let pers = self.persistence + let servers = pers.loadArrayOfDictionariesFromFile("ServerConfigs.json") ?? [] + let mutableServers = servers.map { $0.mutableCopy() as! NSMutableDictionary } + + let withoutPasswords = mutableServers.map { + (d: NSMutableDictionary) -> NSDictionary in + + let password = d.stringForKey("password") + let key = (try! XcodeServerConfig(json: d)).keychainKey() + + let keychain = SecurePersistence.xcodeServerPasswordKeychain() + keychain.writeIfNeeded(key, value: password) + + d.removeObjectForKey("password") + + precondition(keychain.read(key) == password, "Saved password must match") + + return d + } + + pers.saveArray("ServerConfigs.json", items: withoutPasswords) + } + + func migrateLogs() { + + let pers = self.persistence + (pers.filesInFolder(pers.folderForIntention(.Reading)) ?? []) + .map { $0.lastPathComponent ?? "" } + .filter { $0.hasSuffix("log") } + .forEach { + pers.copyFileToFolder($0, folder: "Logs") + pers.deleteFile($0) + } + } +} + diff --git a/BuildaKit/Project.swift b/BuildaKit/Project.swift index 740cc22..09beac1 100644 --- a/BuildaKit/Project.swift +++ b/BuildaKit/Project.swift @@ -75,32 +75,36 @@ public class Project { return try XcodeProjectParser.parseRepoMetadataFromProjectOrWorkspaceURL(url) } - public func githubRepoName() -> String? { + public func serviceRepoName() -> String? { - if let projectUrl = self.workspaceMetadata?.projectURL { - let originalStringUrl = projectUrl.absoluteString - let stringUrl = originalStringUrl.lowercaseString - - /* - both https and ssh repos on github have a form of: - {https://|git@}github.com{:|/}organization/repo.git - here I need the organization/repo bit, which I'll do by finding "github.com" and shifting right by one - and scan up until ".git" - */ - - if let githubRange = stringUrl.rangeOfString("github.com", options: NSStringCompareOptions(), range: nil, locale: nil), - let dotGitRange = stringUrl.rangeOfString(".git", options: NSStringCompareOptions.BackwardsSearch, range: nil, locale: nil) { - - let start = githubRange.endIndex.advancedBy(1) - let end = dotGitRange.startIndex - - let repoName = originalStringUrl.substringWithRange(Range(start: start, end: end)) - return repoName - } + guard let meta = self.workspaceMetadata else { return nil } + + let projectUrl = meta.projectURL + let service = meta.service + + let originalStringUrl = projectUrl.absoluteString + let stringUrl = originalStringUrl.lowercaseString + + /* + both https and ssh repos on github have a form of: + {https://|git@}SERVICE_URL{:|/}organization/repo.git + here I need the organization/repo bit, which I'll do by finding "SERVICE_URL" and shifting right by one + and scan up until ".git" + */ + + let serviceUrl = service.hostname().lowercaseString + if let githubRange = stringUrl.rangeOfString(serviceUrl, options: NSStringCompareOptions(), range: nil, locale: nil), + let dotGitRange = stringUrl.rangeOfString(".git", options: NSStringCompareOptions.BackwardsSearch, range: nil, locale: nil) { + + let start = githubRange.endIndex.advancedBy(1) + let end = dotGitRange.startIndex + + let repoName = originalStringUrl.substringWithRange(Range(start: start, end: end)) + return repoName } return nil } - + private func getContentsOfKeyAtPath(path: String) -> String? { let url = NSURL(fileURLWithPath: path) diff --git a/BuildaKit/ProjectConfig.swift b/BuildaKit/ProjectConfig.swift index 7727928..292d437 100644 --- a/BuildaKit/ProjectConfig.swift +++ b/BuildaKit/ProjectConfig.swift @@ -8,21 +8,23 @@ import Foundation import BuildaUtils +import BuildaGitServer public struct ProjectConfig { public let id: RefType public var url: String - public var githubToken: String public var privateSSHKeyPath: String public var publicSSHKeyPath: String - public var sshPassphrase: String? + + public var sshPassphrase: String? //loaded from the keychain + public var serverAuthentication: ProjectAuthenticator? //loaded from the keychain //creates a new default ProjectConfig public init() { self.id = Ref.new() self.url = "" - self.githubToken = "" + self.serverAuthentication = nil self.privateSSHKeyPath = "" self.publicSSHKeyPath = "" self.sshPassphrase = nil @@ -36,10 +38,8 @@ public struct ProjectConfig { private struct Keys { static let URL = "url" - static let GitHubToken = "github_token" static let PrivateSSHKeyPath = "ssh_private_key_url" static let PublicSSHKeyPath = "ssh_public_key_url" - static let SSHPassphrase = "ssh_passphrase" static let Id = "id" } @@ -50,22 +50,18 @@ extension ProjectConfig: JSONSerializable { let json = NSMutableDictionary() json[Keys.URL] = self.url - json[Keys.GitHubToken] = self.githubToken json[Keys.PrivateSSHKeyPath] = self.privateSSHKeyPath json[Keys.PublicSSHKeyPath] = self.publicSSHKeyPath json[Keys.Id] = self.id - json.optionallyAddValueForKey(self.sshPassphrase, key: "ssh_passphrase") return json } public init(json: NSDictionary) throws { self.url = try json.get(Keys.URL) - self.githubToken = try json.get(Keys.GitHubToken) self.privateSSHKeyPath = try json.get(Keys.PrivateSSHKeyPath) self.publicSSHKeyPath = try json.get(Keys.PublicSSHKeyPath) self.id = try json.get(Keys.Id) - self.sshPassphrase = try json.getOptionally(Keys.SSHPassphrase) } } diff --git a/BuildaKit/SecurePersistence.swift b/BuildaKit/SecurePersistence.swift new file mode 100644 index 0000000..a7ac93a --- /dev/null +++ b/BuildaKit/SecurePersistence.swift @@ -0,0 +1,110 @@ +// +// SecurePersistence.swift +// Buildasaur +// +// Created by Honza Dvorsky on 1/22/16. +// Copyright © 2016 Honza Dvorsky. All rights reserved. +// + +import Foundation +import KeychainAccess +import XcodeServerSDK +import SwiftSafe + +final class SecurePersistence { + + #if TESTING + typealias Keychain = NSMutableDictionary + #endif + + static let Prefix = "com.honzadvorsky.buildasaur" + + private let keychain: Keychain + private let safe: Safe + + private init(keychain: Keychain, safe: Safe = EREW()) { + self.keychain = keychain + self.safe = safe + } + + static func xcodeServerPasswordKeychain() -> SecurePersistence { + return self.keychain("\(Prefix).xcs.password") + } + + static func sourceServerTokenKeychain() -> SecurePersistence { + return self.keychain("\(Prefix).source_server.oauth_tokens") + } + + static func sourceServerPassphraseKeychain() -> SecurePersistence { + return self.keychain("\(Prefix).source_server.passphrase") + } + + static private func keychain(service: String) -> SecurePersistence { + #if TESTING + let keychain = NSMutableDictionary() + #else + let keychain = Keychain(service: service) + #endif + return self.init(keychain: keychain) + } + + func read(key: String) -> String? { + var val: String? + self.safe.read { + #if TESTING + val = self.keychain[key] as? String + #else + val = self.keychain[key] + #endif + } + return val + } + + func readAll() -> [(String, String)] { + var all: [(String, String)] = [] + self.safe.read { + #if TESTING + let keychain = self.keychain + all = keychain.allKeys.map { ($0 as! String, keychain[$0 as! String] as! String) } + #else + let keychain = self.keychain + all = keychain.allKeys().map { ($0, keychain[$0]!) } + #endif + } + return all + } + + func writeIfNeeded(key: String, value: String?) { + self.safe.write { + self.updateIfNeeded(key, value: value) + } + } + + private func updateIfNeeded(key: String, value: String?) { + #if TESTING + let existing = self.keychain[key] as? String + #else + let existing = self.keychain[key] + #endif + if existing != value { + self.keychain[key] = value + } + } +} + +public protocol KeychainSaveable { + func keychainKey() -> String +} + +extension XcodeServerConfig: KeychainSaveable { + public func keychainKey() -> String { + return "\(self.host):\(self.user ?? "")" + } +} + +extension ProjectConfig: KeychainSaveable { + public func keychainKey() -> String { + return self.id + } +} + diff --git a/BuildaKit/HDGitHubXCBotSyncer.swift b/BuildaKit/StandardSyncer.swift similarity index 82% rename from BuildaKit/HDGitHubXCBotSyncer.swift rename to BuildaKit/StandardSyncer.swift index b1bf7a9..6e5055c 100644 --- a/BuildaKit/HDGitHubXCBotSyncer.swift +++ b/BuildaKit/StandardSyncer.swift @@ -1,5 +1,5 @@ // -// HDGitHubXCBotSyncer.swift +// StandardSyncer.swift // Buildasaur // // Created by Honza Dvorsky on 15/02/2015. @@ -11,9 +11,9 @@ import BuildaGitServer import XcodeServerSDK import ReactiveCocoa -public class HDGitHubXCBotSyncer : Syncer { +public class StandardSyncer : Syncer { - public var github: GitHubServer + public var sourceServer: SourceServerType public var xcodeServer: XcodeServer public var project: Project public var buildTemplate: BuildTemplate @@ -25,11 +25,11 @@ public class HDGitHubXCBotSyncer : Syncer { return ConfigTriplet(syncer: self.config.value, server: self.xcodeServer.config, project: self.project.config.value, buildTemplate: self.buildTemplate, triggers: self.triggers.map { $0.config }) } - public init(integrationServer: XcodeServer, sourceServer: GitHubServer, project: Project, buildTemplate: BuildTemplate, triggers: [Trigger], config: SyncerConfig) { + public init(integrationServer: XcodeServer, sourceServer: SourceServerType, project: Project, buildTemplate: BuildTemplate, triggers: [Trigger], config: SyncerConfig) { self.config = MutableProperty(config) - self.github = sourceServer + self.sourceServer = sourceServer self.xcodeServer = integrationServer self.project = project self.buildTemplate = buildTemplate diff --git a/BuildaKit/StorageManager.swift b/BuildaKit/StorageManager.swift index b3c6dc6..5851a09 100644 --- a/BuildaKit/StorageManager.swift +++ b/BuildaKit/StorageManager.swift @@ -7,10 +7,10 @@ // import Foundation -import BuildaGitServer import BuildaUtils import XcodeServerSDK import ReactiveCocoa +import BuildaGitServer public enum StorageManagerError: ErrorType { case DuplicateServerConfig(XcodeServerConfig) @@ -26,9 +26,14 @@ public class StorageManager { public let triggerConfigs = MutableProperty<[String: TriggerConfig]>([:]) public let config = MutableProperty<[String: AnyObject]>([:]) + let tokenKeychain = SecurePersistence.sourceServerTokenKeychain() + let passphraseKeychain = SecurePersistence.sourceServerPassphraseKeychain() + let serverConfigKeychain = SecurePersistence.xcodeServerPasswordKeychain() + private let persistence: Persistence public init(persistence: Persistence) { + self.persistence = persistence self.loadAllFromPersistence() self.setupSaving() @@ -167,10 +172,33 @@ public class StorageManager { private func loadAllFromPersistence() { self.config.value = self.persistence.loadDictionaryFromFile("Config.json") ?? [:] + let allProjects: [ProjectConfig] = self.persistence.loadArrayFromFile("Projects.json") ?? [] - self.projectConfigs.value = allProjects.dictionarifyWithKey { $0.id } + //load server token & ssh passphrase from keychain + let tokenKeychain = self.tokenKeychain + let passphraseKeychain = self.passphraseKeychain + self.projectConfigs.value = allProjects + .map { + (var p: ProjectConfig) -> ProjectConfig in + var auth: ProjectAuthenticator? + if let val = tokenKeychain.read(p.keychainKey()) { + auth = try? ProjectAuthenticator.fromString(val) + } + p.serverAuthentication = auth + p.sshPassphrase = passphraseKeychain.read(p.keychainKey()) + return p + }.dictionarifyWithKey { $0.id } + let allServerConfigs: [XcodeServerConfig] = self.persistence.loadArrayFromFile("ServerConfigs.json") ?? [] - self.serverConfigs.value = allServerConfigs.dictionarifyWithKey { $0.id } + //load xcs passwords from keychain + let xcsConfigKeychain = self.serverConfigKeychain + self.serverConfigs.value = allServerConfigs + .map { + (var x: XcodeServerConfig) -> XcodeServerConfig in + x.password = xcsConfigKeychain.read(x.keychainKey()) + return x + }.dictionarifyWithKey { $0.id } + let allTemplates: [BuildTemplate] = self.persistence.loadArrayFromFolder("BuildTemplates") ?? [] self.buildTemplates.value = allTemplates.dictionarifyWithKey { $0.id } let allTriggers: [TriggerConfig] = self.persistence.loadArrayFromFolder("Triggers") ?? [] @@ -211,11 +239,23 @@ public class StorageManager { private func saveProjectConfigs(configs: [String: ProjectConfig]) { let projectConfigs: NSArray = Array(configs.values).map { $0.jsonify() } + let tokenKeychain = SecurePersistence.sourceServerTokenKeychain() + let passphraseKeychain = SecurePersistence.sourceServerPassphraseKeychain() + configs.values.forEach { + if let auth = $0.serverAuthentication { + tokenKeychain.writeIfNeeded($0.keychainKey(), value: auth.toString()) + } + passphraseKeychain.writeIfNeeded($0.keychainKey(), value: $0.sshPassphrase) + } self.persistence.saveArray("Projects.json", items: projectConfigs) } private func saveServerConfigs(configs: [String: XcodeServerConfig]) { let serverConfigs = Array(configs.values).map { $0.jsonify() } + let serverConfigKeychain = SecurePersistence.xcodeServerPasswordKeychain() + configs.values.forEach { + serverConfigKeychain.writeIfNeeded($0.keychainKey(), value: $0.password) + } self.persistence.saveArray("ServerConfigs.json", items: serverConfigs) } @@ -247,6 +287,21 @@ public class StorageManager { } } +extension StorageManager: SyncerLifetimeChangeObserver { + + public func authChanged(projectConfigId: String, auth: ProjectAuthenticator) { + + //and modify in the owner's config + var config = self.projectConfigs.value[projectConfigId]! + + //auth info changed, re-save it into the keychain + self.tokenKeychain.writeIfNeeded(config.keychainKey(), value: auth.toString()) + + config.serverAuthentication = auth + self.projectConfigs.value[projectConfigId] = config + } +} + //HACK: move to XcodeServerSDK extension TriggerConfig: JSONReadable, JSONWritable { public func jsonify() -> NSDictionary { diff --git a/BuildaKit/SummaryBuilder.swift b/BuildaKit/SummaryBuilder.swift index 1e7b9d0..a65051f 100644 --- a/BuildaKit/SummaryBuilder.swift +++ b/BuildaKit/SummaryBuilder.swift @@ -13,6 +13,7 @@ import BuildaGitServer class SummaryBuilder { + var statusCreator: BuildStatusCreator! var lines: [String] = [] let resultString: String var linkBuilder: (Integration) -> String? = { _ in nil } @@ -23,12 +24,12 @@ class SummaryBuilder { //MARK: high level - func buildPassing(integration: Integration) -> HDGitHubXCBotSyncer.GitHubStatusAndComment { + func buildPassing(integration: Integration) -> StatusAndComment { let linkToIntegration = self.linkBuilder(integration) self.addBaseCommentFromIntegration(integration) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Success, description: "Build passed!", targetUrl: linkToIntegration) + let status = self.createStatus(.Success, description: "Build passed!", targetUrl: linkToIntegration) let buildResultSummary = integration.buildResultSummary! if integration.result == .Succeeded { @@ -45,49 +46,55 @@ class SummaryBuilder { return self.buildWithStatus(status) } - func buildFailingTests(integration: Integration) -> HDGitHubXCBotSyncer.GitHubStatusAndComment { + func buildFailingTests(integration: Integration) -> StatusAndComment { let linkToIntegration = self.linkBuilder(integration) self.addBaseCommentFromIntegration(integration) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Failure, description: "Build failed tests!", targetUrl: linkToIntegration) + let status = self.createStatus(.Failure, description: "Build failed tests!", targetUrl: linkToIntegration) let buildResultSummary = integration.buildResultSummary! self.appendTestFailure(buildResultSummary) return self.buildWithStatus(status) } - func buildErrorredIntegration(integration: Integration) -> HDGitHubXCBotSyncer.GitHubStatusAndComment { + func buildErrorredIntegration(integration: Integration) -> StatusAndComment { let linkToIntegration = self.linkBuilder(integration) self.addBaseCommentFromIntegration(integration) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "Build error!", targetUrl: linkToIntegration) + let status = self.createStatus(.Error, description: "Build error!", targetUrl: linkToIntegration) self.appendErrors(integration) return self.buildWithStatus(status) } - func buildCanceledIntegration(integration: Integration) -> HDGitHubXCBotSyncer.GitHubStatusAndComment { + func buildCanceledIntegration(integration: Integration) -> StatusAndComment { let linkToIntegration = self.linkBuilder(integration) self.addBaseCommentFromIntegration(integration) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "Build canceled!", targetUrl: linkToIntegration) + let status = self.createStatus(.Error, description: "Build canceled!", targetUrl: linkToIntegration) self.appendCancel() return self.buildWithStatus(status) } - func buildEmptyIntegration() -> HDGitHubXCBotSyncer.GitHubStatusAndComment { + func buildEmptyIntegration() -> StatusAndComment { - let status = HDGitHubXCBotSyncer.createStatusFromState(.NoState, description: nil, targetUrl: nil) - return (status: status, comment: nil) + let status = self.createStatus(.NoState, description: nil, targetUrl: nil) + return self.buildWithStatus(status) } //MARK: utils + private func createStatus(state: BuildState, description: String?, targetUrl: String?) -> StatusType { + + let status = self.statusCreator.createStatusFromState(state, description: description, targetUrl: targetUrl) + return status + } + func addBaseCommentFromIntegration(integration: Integration) { var integrationText = "Integration \(integration.number)" @@ -152,7 +159,7 @@ class SummaryBuilder { self.lines.append("Build was **manually canceled**.") } - func buildWithStatus(status: Status) -> HDGitHubXCBotSyncer.GitHubStatusAndComment { + func buildWithStatus(status: StatusType) -> StatusAndComment { let comment: String? if lines.count == 0 { @@ -160,7 +167,7 @@ class SummaryBuilder { } else { comment = lines.joinWithSeparator("\n") } - return (status: status, comment: comment) + return StatusAndComment(status: status, comment: comment) } } diff --git a/BuildaKit/SyncPair.swift b/BuildaKit/SyncPair.swift index b187a0f..bf30e99 100644 --- a/BuildaKit/SyncPair.swift +++ b/BuildaKit/SyncPair.swift @@ -18,7 +18,7 @@ import XcodeServerSDK */ public class SyncPair { - var syncer: HDGitHubXCBotSyncer! + var syncer: StandardSyncer! init() { // diff --git a/BuildaKit/SyncPairExtensions.swift b/BuildaKit/SyncPairExtensions.swift index e1b6f4a..41c4fe7 100644 --- a/BuildaKit/SyncPairExtensions.swift +++ b/BuildaKit/SyncPairExtensions.swift @@ -15,7 +15,7 @@ extension SyncPair { public struct Actions { public let integrationsToCancel: [Integration]? - public let githubStatusToSet: (status: HDGitHubXCBotSyncer.GitHubStatusAndComment, commit: String, issue: Issue?)? + public let statusToSet: (status: StatusAndComment, commit: String, issue: IssueType?)? public let startNewIntegrationBot: Bot? //if non-nil, starts a new integration on this bot } @@ -32,7 +32,7 @@ extension SyncPair { }) } - if let newStatus = actions.githubStatusToSet { + if let newStatus = actions.statusToSet { let status = newStatus.status let commit = newStatus.commit diff --git a/BuildaKit/SyncPairResolver.swift b/BuildaKit/SyncPairResolver.swift index 4cb481d..8881793 100644 --- a/BuildaKit/SyncPairResolver.swift +++ b/BuildaKit/SyncPairResolver.swift @@ -19,9 +19,10 @@ public class SyncPairResolver { public func resolveActionsForCommitAndIssueWithBotIntegrations( commit: String, - issue: Issue?, + issue: IssueType?, bot: Bot, hostname: String, + buildStatusCreator: BuildStatusCreator, integrations: [Integration]) -> SyncPair.Actions { var integrationsToCancel: [Integration] = [] @@ -76,7 +77,7 @@ public class SyncPairResolver { //A1. - it's empty, kick off an integration for the latest commit return SyncPair.Actions( integrationsToCancel: integrationsToCancel, - githubStatusToSet: nil, + statusToSet: nil, startNewIntegrationBot: bot ) } @@ -126,12 +127,13 @@ public class SyncPairResolver { pending: latestPendingIntegration, running: runningIntegration, link: link, + statusCreator: buildStatusCreator, completed: completedIntegrations) //merge in nested actions return SyncPair.Actions( integrationsToCancel: integrationsToCancel + (actions.integrationsToCancel ?? []), - githubStatusToSet: actions.githubStatusToSet, + statusToSet: actions.statusToSet, startNewIntegrationBot: actions.startNewIntegrationBot ?? (startNewIntegration ? bot : nil) ) } @@ -146,7 +148,7 @@ public class SyncPairResolver { //if it's not pending, we need to take a look at the blueprint and inspect the SHA. if let blueprint = integration.blueprint, let sha = blueprint.commitSHA { - return sha == headCommit + return sha.hasPrefix(headCommit) //headCommit is sometimes a short version only } //when an integration is Pending, Preparing or Checking out, it doesn't have a blueprint, but it is, by definition, a headCommit @@ -206,13 +208,14 @@ public class SyncPairResolver { func resolveCommitStatusFromLatestIntegrations( commit: String, - issue: Issue?, + issue: IssueType?, pending: Integration?, running: Integration?, link: (Integration) -> String?, + statusCreator: BuildStatusCreator, completed: Set) -> SyncPair.Actions { - let statusWithComment: HDGitHubXCBotSyncer.GitHubStatusAndComment + let statusWithComment: StatusAndComment var integrationsToCancel: [Integration] = [] //if there's any pending integration, we're ["Pending" - Waiting in the queue] @@ -220,8 +223,8 @@ public class SyncPairResolver { //TODO: show how many builds are ahead in the queue and estimate when it will be //started and when finished? (there is an average running time on each bot, it should be easy) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Build waiting in the queue...", targetUrl: link(pending)) - statusWithComment = (status: status, comment: nil) + let status = statusCreator.createStatusFromState(.Pending, description: "Build waiting in the queue...", targetUrl: link(pending)) + statusWithComment = StatusAndComment(status: status, comment: nil) //also, cancel the running integration, if it's there any if let running = running { @@ -235,8 +238,8 @@ public class SyncPairResolver { //there is a running integration. //TODO: estimate, based on the average running time of this bot and on the started timestamp, when it will finish. add that to the description. let currentStepString = running.currentStep.rawValue - let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Integration step: \(currentStepString)...", targetUrl: link(running)) - statusWithComment = (status: status, comment: nil) + let status = statusCreator.createStatusFromState(.Pending, description: "Integration step: \(currentStepString)...", targetUrl: link(running)) + statusWithComment = StatusAndComment(status: status, comment: nil) } else { @@ -244,33 +247,35 @@ public class SyncPairResolver { if completed.count > 0 { //we have some completed integrations - statusWithComment = self.resolveStatusFromCompletedIntegrations(completed, link: link) + statusWithComment = self.resolveStatusFromCompletedIntegrations(completed, statusCreator: statusCreator, link: link) } else { //this shouldn't happen. Log.error("LOGIC ERROR! This shouldn't happen, there are no completed integrations!") - let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "* UNKNOWN STATE, Builda ERROR *", targetUrl: nil) - statusWithComment = (status: status, "Builda error, unknown state!") + let status = statusCreator.createStatusFromState(.Error, description: "* UNKNOWN STATE, Builda ERROR *", targetUrl: nil) + statusWithComment = StatusAndComment(status: status, comment: "Builda error, unknown state!") } } } return SyncPair.Actions( integrationsToCancel: integrationsToCancel, - githubStatusToSet: (status: statusWithComment, commit: commit, issue: issue), + statusToSet: (status: statusWithComment, commit: commit, issue: issue), startNewIntegrationBot: nil ) } func resolveStatusFromCompletedIntegrations( integrations: Set, + statusCreator: BuildStatusCreator, link: (Integration) -> String? - ) -> HDGitHubXCBotSyncer.GitHubStatusAndComment { + ) -> StatusAndComment { //get integrations sorted by number let sortedDesc = Array(integrations).sort { $0.number > $1.number } let summary = SummaryBuilder() summary.linkBuilder = link + summary.statusCreator = statusCreator //if there are any succeeded, it wins - iterating from the end if let passingIntegration = sortedDesc.filter({ diff --git a/BuildaKit/SyncPair_Branch_Bot.swift b/BuildaKit/SyncPair_Branch_Bot.swift index 299f01b..c2f26f9 100644 --- a/BuildaKit/SyncPair_Branch_Bot.swift +++ b/BuildaKit/SyncPair_Branch_Bot.swift @@ -13,11 +13,11 @@ import BuildaUtils public class SyncPair_Branch_Bot: SyncPair { - let branch: Branch + let branch: BranchType let bot: Bot let resolver: SyncPairBranchResolver - public init(branch: Branch, bot: Bot, resolver: SyncPairBranchResolver) { + public init(branch: BranchType, bot: Bot, resolver: SyncPairBranchResolver) { self.branch = branch self.bot = bot self.resolver = resolver @@ -39,8 +39,8 @@ public class SyncPair_Branch_Bot: SyncPair { private func syncBranchWithBot(completion: Completion) { let bot = self.bot - let headCommit = self.branch.commit.sha - let issue: Issue? = nil //TODO: only pull/create if we're failing + let headCommit = self.branch.commitSHA + let issue: IssueType? = nil //TODO: only pull/create if we're failing self.syncer.xcodeServer.getHostname { (hostname, error) -> () in @@ -61,6 +61,7 @@ public class SyncPair_Branch_Bot: SyncPair { issue: issue, bot: bot, hostname: hostname!, + buildStatusCreator: self.syncer, integrations: integrations) //in case of branches, we also (optionally) want to add functionality for creating an issue if the branch starts failing and updating with comments the same way we do with PRs. diff --git a/BuildaKit/SyncPair_Branch_NoBot.swift b/BuildaKit/SyncPair_Branch_NoBot.swift index 0546aad..23caa2d 100644 --- a/BuildaKit/SyncPair_Branch_NoBot.swift +++ b/BuildaKit/SyncPair_Branch_NoBot.swift @@ -12,10 +12,10 @@ import BuildaGitServer class SyncPair_Branch_NoBot: SyncPair { - let branch: Branch - let repo: Repo + let branch: BranchType + let repo: RepoType - init(branch: Branch, repo: Repo) { + init(branch: BranchType, repo: RepoType) { self.branch = branch self.repo = repo super.init() @@ -37,7 +37,7 @@ class SyncPair_Branch_NoBot: SyncPair { //MARK: Internal - private class func createBotForBranch(syncer syncer: HDGitHubXCBotSyncer, branch: Branch, repo: Repo, completion: Completion) { + private class func createBotForBranch(syncer syncer: StandardSyncer, branch: BranchType, repo: RepoType, completion: Completion) { syncer.createBotFromBranch(branch, repo: repo, completion: { () -> () in completion(error: nil) diff --git a/BuildaKit/SyncPair_Deletable_Bot.swift b/BuildaKit/SyncPair_Deletable_Bot.swift index e4e5553..545e2af 100644 --- a/BuildaKit/SyncPair_Deletable_Bot.swift +++ b/BuildaKit/SyncPair_Deletable_Bot.swift @@ -32,7 +32,7 @@ class SyncPair_Deletable_Bot: SyncPair { return "Deletable Bot (\(self.bot.name))" } - private class func deleteBot(syncer syncer: HDGitHubXCBotSyncer, bot: Bot, completion: Completion) { + private class func deleteBot(syncer syncer: StandardSyncer, bot: Bot, completion: Completion) { syncer.deleteBot(bot, completion: { () -> () in completion(error: nil) diff --git a/BuildaKit/SyncPair_PR_Bot.swift b/BuildaKit/SyncPair_PR_Bot.swift index 7e17968..aa2dcdb 100644 --- a/BuildaKit/SyncPair_PR_Bot.swift +++ b/BuildaKit/SyncPair_PR_Bot.swift @@ -13,11 +13,11 @@ import BuildaUtils public class SyncPair_PR_Bot: SyncPair { - let pr: PullRequest + let pr: PullRequestType let bot: Bot public let resolver: SyncPairPRResolver - public init(pr: PullRequest, bot: Bot, resolver: SyncPairPRResolver) { + public init(pr: PullRequestType, bot: Bot, resolver: SyncPairPRResolver) { self.pr = pr self.bot = bot self.resolver = resolver @@ -31,7 +31,7 @@ public class SyncPair_PR_Bot: SyncPair { } override func syncPairName() -> String { - return "PR (\(self.pr.number):\(self.pr.head.ref)) + Bot (\(self.bot.name))" + return "PR (\(self.pr.number):\(self.pr.headName)) + Bot (\(self.bot.name))" } //MARK: Internal @@ -41,7 +41,7 @@ public class SyncPair_PR_Bot: SyncPair { let syncer = self.syncer let bot = self.bot let pr = self.pr - let headCommit = pr.head.sha + let headCommit = pr.headCommitSHA let issue = pr self.getIntegrations(bot, completion: { (integrations, error) -> () in @@ -73,6 +73,7 @@ public class SyncPair_PR_Bot: SyncPair { issue: issue, bot: bot, hostname: hostname!, + buildStatusCreator: self.syncer, integrations: integrations) self.performActions(actions, completion: completion) } @@ -82,8 +83,8 @@ public class SyncPair_PR_Bot: SyncPair { //not enabled, make sure the PR reflects that and the instructions are clear Log.verbose("Bot \(bot.name) is not yet enabled, ignoring...") - let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Waiting for \"lttm\" to start testing", targetUrl: nil) - let notYetEnabled: HDGitHubXCBotSyncer.GitHubStatusAndComment = (status: status, comment: nil) + let status = self.syncer.createStatusFromState(BuildState.Pending, description: "Waiting for \"lttm\" to start testing", targetUrl: nil) + let notYetEnabled = StatusAndComment(status: status, comment: nil) syncer.updateCommitStatusIfNecessary(notYetEnabled, commit: headCommit, issue: pr, completion: completion) } }) @@ -106,11 +107,11 @@ public class SyncPair_PR_Bot: SyncPair { if let repoName = syncer.repoName() { - self.syncer._github.findMatchingCommentInIssue(keyword, issue: self.pr.number, repo: repoName) { + self.syncer.sourceServer.findMatchingCommentInIssue(keyword, issue: self.pr.number, repo: repoName) { (foundComments, error) -> () in if error != nil { - let e = Error.withInfo("Fetching comments", internalError: error) + let e = Error.withInfo("Fetching comments", internalError: error as? NSError) completion(isEnabled: false, error: e) return } diff --git a/BuildaKit/SyncPair_PR_NoBot.swift b/BuildaKit/SyncPair_PR_NoBot.swift index a619ee0..656233c 100644 --- a/BuildaKit/SyncPair_PR_NoBot.swift +++ b/BuildaKit/SyncPair_PR_NoBot.swift @@ -11,9 +11,9 @@ import BuildaGitServer class SyncPair_PR_NoBot: SyncPair { - let pr: PullRequest + let pr: PullRequestType - init(pr: PullRequest) { + init(pr: PullRequestType) { self.pr = pr super.init() } @@ -28,12 +28,12 @@ class SyncPair_PR_NoBot: SyncPair { } override func syncPairName() -> String { - return "PR (\(self.pr.head.ref)) + No Bot" + return "PR (\(self.pr.headName)) + No Bot" } //MARK: Internal - private class func createBotForPR(syncer syncer: HDGitHubXCBotSyncer, pr: PullRequest, completion: Completion) { + private class func createBotForPR(syncer syncer: StandardSyncer, pr: PullRequestType, completion: Completion) { syncer.createBotFromPR(pr, completion: { () -> () in completion(error: nil) diff --git a/BuildaKit/Syncer.swift b/BuildaKit/Syncer.swift index 512202b..106a050 100644 --- a/BuildaKit/Syncer.swift +++ b/BuildaKit/Syncer.swift @@ -7,7 +7,6 @@ // import Foundation -import BuildaGitServer import BuildaUtils import XcodeServerSDK import ReactiveCocoa @@ -144,6 +143,10 @@ class Trampoline: NSObject { self.notifyError(Error.withInfo(errorString), context: context) } + func notifyError(error: ErrorType?, context: String?) { + self.notifyError(error as? NSError, context: context) + } + func notifyError(error: NSError?, context: String?) { var message = "Syncing encountered a problem. " diff --git a/BuildaKit/SyncerBotManipulation.swift b/BuildaKit/SyncerBotManipulation.swift index 23a8330..fdc2db4 100644 --- a/BuildaKit/SyncerBotManipulation.swift +++ b/BuildaKit/SyncerBotManipulation.swift @@ -11,7 +11,7 @@ import XcodeServerSDK import BuildaGitServer import BuildaUtils -extension HDGitHubXCBotSyncer { +extension StandardSyncer { //MARK: Bot manipulation utils @@ -44,7 +44,7 @@ extension HDGitHubXCBotSyncer { }) } - private func createBotFromName(botName: String, branch: String, repo: Repo, completion: () -> ()) { + private func createBotFromName(botName: String, branch: String, repo: RepoType, completion: () -> ()) { /* synced bots must have a manual schedule, Builda tells the bot to reintegrate in case of a new commit. @@ -59,7 +59,7 @@ extension HDGitHubXCBotSyncer { let template = self.buildTemplate //to handle forks - let headOriginUrl = repo.repoUrlSSH + let headOriginUrl = repo.originUrlSSH let localProjectOriginUrl = self._project.workspaceMetadata!.projectURL.absoluteString let project: Project @@ -90,15 +90,15 @@ extension HDGitHubXCBotSyncer { } } - func createBotFromPR(pr: PullRequest, completion: () -> ()) { + func createBotFromPR(pr: PullRequestType, completion: () -> ()) { - let branchName = pr.head.ref + let branchName = pr.headName let botName = BotNaming.nameForBotWithPR(pr, repoName: self.repoName()!) - self.createBotFromName(botName, branch: branchName, repo: pr.head.repo, completion: completion) + self.createBotFromName(botName, branch: branchName, repo: pr.headRepo, completion: completion) } - func createBotFromBranch(branch: Branch, repo: Repo, completion: () -> ()) { + func createBotFromBranch(branch: BranchType, repo: RepoType, completion: () -> ()) { let branchName = branch.name let botName = BotNaming.nameForBotWithBranch(branch, repoName: self.repoName()!) diff --git a/BuildaKit/SyncerBotNaming.swift b/BuildaKit/SyncerBotNaming.swift index 68139ef..21dafd9 100644 --- a/BuildaKit/SyncerBotNaming.swift +++ b/BuildaKit/SyncerBotNaming.swift @@ -20,11 +20,11 @@ class BotNaming { return bot.name.hasPrefix(self.prefixForBuildaBotInRepoWithName(repoName)) } - class func nameForBotWithBranch(branch: Branch, repoName: String) -> String { + class func nameForBotWithBranch(branch: BranchType, repoName: String) -> String { return "\(self.prefixForBuildaBotInRepoWithName(repoName)) |-> \(branch.name)" } - class func nameForBotWithPR(pr: PullRequest, repoName: String) -> String { + class func nameForBotWithPR(pr: PullRequestType, repoName: String) -> String { return "\(self.prefixForBuildaBotInRepoWithName(repoName)) PR #\(pr.number)" } diff --git a/BuildaKit/SyncerFactory.swift b/BuildaKit/SyncerFactory.swift index cbc8038..63e2ff8 100644 --- a/BuildaKit/SyncerFactory.swift +++ b/BuildaKit/SyncerFactory.swift @@ -11,46 +11,66 @@ import XcodeServerSDK import BuildaGitServer public protocol SyncerFactoryType { - func createSyncers(configs: [ConfigTriplet]) -> [HDGitHubXCBotSyncer] + func createSyncers(configs: [ConfigTriplet]) -> [StandardSyncer] func defaultConfigTriplet() -> ConfigTriplet func newEditableTriplet() -> EditableConfigTriplet func createXcodeServer(config: XcodeServerConfig) -> XcodeServer func createProject(config: ProjectConfig) -> Project? - func createSourceServer(token: String) -> GitHubServer + func createSourceServer(service: GitService, auth: ProjectAuthenticator?) -> SourceServerType func createTrigger(config: TriggerConfig) -> Trigger } +public protocol SyncerLifetimeChangeObserver { + func authChanged(projectConfigId: String, auth: ProjectAuthenticator) +} + public class SyncerFactory: SyncerFactoryType { - private var syncerPool = [RefType: HDGitHubXCBotSyncer]() + private var syncerPool = [RefType: StandardSyncer]() private var projectPool = [RefType: Project]() private var xcodeServerPool = [RefType: XcodeServer]() + public var syncerLifetimeChangeObserver: SyncerLifetimeChangeObserver! + public init() { } - private func createSyncer(triplet: ConfigTriplet) -> HDGitHubXCBotSyncer? { + private func createSyncer(triplet: ConfigTriplet) -> StandardSyncer? { + + precondition(self.syncerLifetimeChangeObserver != nil) let xcodeServer = self.createXcodeServer(triplet.server) - let githubServer = self.createSourceServer(triplet.project.githubToken) let maybeProject = self.createProject(triplet.project) let triggers = triplet.triggers.map { self.createTrigger($0) } guard let project = maybeProject else { return nil } + guard let service = project.workspaceMetadata?.service else { return nil } + + let projectConfig = triplet.project + let sourceServer = self.createSourceServer(service, auth: projectConfig.serverAuthentication) + sourceServer + .authChangedSignal() + .ignoreNil() + .observeNext { [weak self] (auth) -> () in + self? + .syncerLifetimeChangeObserver + .authChanged(projectConfig.id, auth: auth) + } + if let poolAttempt = self.syncerPool[triplet.syncer.id] { poolAttempt.config.value = triplet.syncer poolAttempt.xcodeServer = xcodeServer - poolAttempt.github = githubServer + poolAttempt.sourceServer = sourceServer poolAttempt.project = project poolAttempt.buildTemplate = triplet.buildTemplate poolAttempt.triggers = triggers return poolAttempt } - let syncer = HDGitHubXCBotSyncer( + let syncer = StandardSyncer( integrationServer: xcodeServer, - sourceServer: githubServer, + sourceServer: sourceServer, project: project, buildTemplate: triplet.buildTemplate, triggers: triggers, @@ -62,7 +82,7 @@ public class SyncerFactory: SyncerFactoryType { return syncer } - public func createSyncers(configs: [ConfigTriplet]) -> [HDGitHubXCBotSyncer] { + public func createSyncers(configs: [ConfigTriplet]) -> [StandardSyncer] { //create syncers let created = configs.map { self.createSyncer($0) }.filter { $0 != nil }.map { $0! } @@ -117,8 +137,9 @@ public class SyncerFactory: SyncerFactoryType { return project } - public func createSourceServer(token: String) -> GitHubServer { - let server = GitHubFactory.server(token) + public func createSourceServer(service: GitService, auth: ProjectAuthenticator?) -> SourceServerType { + + let server = SourceServerFactory().createServer(service, auth: auth) return server } diff --git a/BuildaKit/SyncerGitHubUtils.swift b/BuildaKit/SyncerGitHubUtils.swift index b36035a..4427237 100644 --- a/BuildaKit/SyncerGitHubUtils.swift +++ b/BuildaKit/SyncerGitHubUtils.swift @@ -10,31 +10,32 @@ import Foundation import BuildaGitServer import BuildaUtils -extension HDGitHubXCBotSyncer { +extension StandardSyncer: BuildStatusCreator { - class func createStatusFromState(state: Status.State, description: String?, targetUrl: String?) -> Status { + public func createStatusFromState(state: BuildState, description: String?, targetUrl: String?) -> StatusType { - //TODO: potentially have multiple contexts to show multiple stats on the PR - let context = "Buildasaur" - return Status(state: state, description: description, targetUrl: targetUrl, context: context) + return self._sourceServer.createStatusFromState(state, description: description, targetUrl: targetUrl) } +} + +extension StandardSyncer { func updateCommitStatusIfNecessary( - newStatus: GitHubStatusAndComment, + newStatus: StatusAndComment, commit: String, - issue: Issue?, + issue: IssueType?, completion: SyncPair.Completion) { let repoName = self.repoName()! - self._github.getStatusOfCommit(commit, repo: repoName, completion: { (status, error) -> () in + self._sourceServer.getStatusOfCommit(commit, repo: repoName, completion: { (status, error) -> () in if error != nil { - let e = Error.withInfo("Commit \(commit) failed to return status", internalError: error) + let e = Error.withInfo("Commit \(commit) failed to return status", internalError: error as? NSError) completion(error: e) return } - if status == nil || newStatus.status != status! { + if status == nil || !newStatus.status.isEqual(status!) { //TODO: add logic for handling the creation of a new Issue for branch tracking //and the deletion of it when build succeeds etc. @@ -47,12 +48,12 @@ extension HDGitHubXCBotSyncer { }) } - func postStatusWithComment(statusWithComment: GitHubStatusAndComment, commit: String, repo: String, issue: Issue?, completion: SyncPair.Completion) { + func postStatusWithComment(statusWithComment: StatusAndComment, commit: String, repo: String, issue: IssueType?, completion: SyncPair.Completion) { - self._github.postStatusOfCommit(statusWithComment.status, sha: commit, repo: repo) { (status, error) -> () in + self._sourceServer.postStatusOfCommit(commit, status: statusWithComment.status, repo: repo) { (status, error) -> () in if error != nil { - let e = Error.withInfo("Failed to post a status on commit \(commit) of repo \(repo)", internalError: error) + let e = Error.withInfo("Failed to post a status on commit \(commit) of repo \(repo)", internalError: error as? NSError) completion(error: e) return } @@ -66,10 +67,10 @@ extension HDGitHubXCBotSyncer { let comment = statusWithComment.comment where postStatusComments { //we have a comment, post it - self._github.postCommentOnIssue(comment, issueNumber: issue.number, repo: repo, completion: { (comment, error) -> () in + self._sourceServer.postCommentOnIssue(comment, issueNumber: issue.number, repo: repo, completion: { (comment, error) -> () in if error != nil { - let e = Error.withInfo("Failed to post a comment \"\(comment)\" on Issue \(issue.number) of repo \(repo)", internalError: error) + let e = Error.withInfo("Failed to post a comment \"\(comment)\" on Issue \(issue.number) of repo \(repo)", internalError: error as? NSError) completion(error: e) } else { completion(error: nil) diff --git a/BuildaKit/SyncerLogic.swift b/BuildaKit/SyncerLogic.swift index 70a03c6..3a6c111 100644 --- a/BuildaKit/SyncerLogic.swift +++ b/BuildaKit/SyncerLogic.swift @@ -11,32 +11,40 @@ import BuildaGitServer import XcodeServerSDK import BuildaUtils -extension HDGitHubXCBotSyncer { +public struct StatusAndComment { + public let status: StatusType + public let comment: String? + + public init(status: StatusType, comment: String? = nil) { + self.status = status + self.comment = comment + } +} + +extension StandardSyncer { var _project: Project { return self.project } var _xcodeServer: XcodeServer { return self.xcodeServer } - var _github: GitHubServer { return self.github } + var _sourceServer: SourceServerType { return self.sourceServer } var _buildTemplate: BuildTemplate { return self.buildTemplate } var _waitForLttm: Bool { return self.config.value.waitForLttm } var _postStatusComments: Bool { return self.config.value.postStatusComments } var _watchedBranchNames: [String] { return self.config.value.watchedBranchNames } public typealias BotActions = ( - prsToSync: [(pr: PullRequest, bot: Bot)], - prBotsToCreate: [PullRequest], - branchesToSync: [(branch: Branch, bot: Bot)], - branchBotsToCreate: [Branch], + prsToSync: [(pr: PullRequestType, bot: Bot)], + prBotsToCreate: [PullRequestType], + branchesToSync: [(branch: BranchType, bot: Bot)], + branchBotsToCreate: [BranchType], botsToDelete: [Bot]) - - public typealias GitHubStatusAndComment = (status: Status, comment: String?) - + public func repoName() -> String? { - return self._project.githubRepoName() + return self._project.serviceRepoName() } internal func syncRepoWithName(repoName: String, completion: () -> ()) { - self._github.getRepo(repoName, completion: { (repo, error) -> () in + self._sourceServer.getRepo(repoName, completion: { (repo, error) -> () in if error != nil { //whoops, no more syncing for now @@ -55,10 +63,10 @@ extension HDGitHubXCBotSyncer { }) } - private func syncRepoWithNameAndMetadata(repoName: String, repo: Repo, completion: () -> ()) { + private func syncRepoWithNameAndMetadata(repoName: String, repo: RepoType, completion: () -> ()) { - //pull PRs from github - self._github.getOpenPullRequests(repoName, completion: { (prs, error) -> () in + //pull PRs from source server + self._sourceServer.getOpenPullRequests(repoName, completion: { (prs, error) -> () in if error != nil { //whoops, no more syncing for now @@ -79,14 +87,14 @@ extension HDGitHubXCBotSyncer { }) } - private func syncRepoWithPRs(repoName: String, repo: Repo, prs: [PullRequest], completion: () -> ()) { + private func syncRepoWithPRs(repoName: String, repo: RepoType, prs: [PullRequestType], completion: () -> ()) { //only fetch branches if there are any watched ones. there might be tens or hundreds of branches //so we don't want to fetch them unless user actually is watching any non-PR branches. if self._watchedBranchNames.count > 0 { //we have PRs, now fetch branches - self._github.getBranchesOfRepo(repoName, completion: { (branches, error) -> () in + self._sourceServer.getBranchesOfRepo(repoName, completion: { (branches, error) -> () in if error != nil { //whoops, no more syncing for now @@ -110,7 +118,7 @@ extension HDGitHubXCBotSyncer { } } - private func syncRepoWithPRsAndBranches(repoName: String, repo: Repo, prs: [PullRequest], branches: [Branch], completion: () -> ()) { + private func syncRepoWithPRsAndBranches(repoName: String, repo: RepoType, prs: [PullRequestType], branches: [BranchType], completion: () -> ()) { //we have branches, now fetch bots self._xcodeServer.getBots({ (bots, error) -> () in @@ -129,12 +137,12 @@ extension HDGitHubXCBotSyncer { //we have both PRs and Bots, resolve self.syncPRsAndBranchesAndBots(repo: repo, repoName: repoName, prs: prs, branches: branches, bots: bots, completion: { - //everything is done, report the damage of GitHub rate limit - if let rateLimitInfo = self._github.latestRateLimitInfo { + //everything is done, report the damage of the server's rate limit + if let rateLimitInfo = repo.latestRateLimitInfo { - let report = rateLimitInfo.getReport() - self.reports["GitHub Rate Limit"] = report - Log.info("GitHub Rate Limit: \(report)") + let report = rateLimitInfo.report + self.reports["Rate Limit"] = report + Log.info("Rate Limit: \(report)") } completion() @@ -146,11 +154,15 @@ extension HDGitHubXCBotSyncer { }) } - public func syncPRsAndBranchesAndBots(repo repo: Repo, repoName: String, prs: [PullRequest], branches: [Branch], bots: [Bot], completion: () -> ()) { + public func syncPRsAndBranchesAndBots(repo repo: RepoType, repoName: String, prs: [PullRequestType], branches: [BranchType], bots: [Bot], completion: () -> ()) { - 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") + let prsDescription = prs.map { (pr: PullRequestType) -> String in + " PR \(pr.number): \(pr.title) [\(pr.headName) -> \(pr.baseName)]" + }.joinWithSeparator("\n") + let branchesDescription = branches.map { (branch: BranchType) -> String in + " Branch [\(branch.name):\(branch.commitSHA)]" } + .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 @@ -165,8 +177,8 @@ extension HDGitHubXCBotSyncer { public func resolvePRsAndBranchesAndBots( repoName repoName: String, - prs: [PullRequest], - branches: [Branch], + prs: [PullRequestType], + branches: [BranchType], bots: [Bot]) -> BotActions { @@ -178,16 +190,16 @@ extension HDGitHubXCBotSyncer { var mappedBots = buildaBots.toDictionary({ $0.name }) //PRs that also have a bot, prsToSync - var prsToSync: [(pr: PullRequest, bot: Bot)] = [] + var prsToSync: [(pr: PullRequestType, bot: Bot)] = [] //branches that also have a bot, branchesToSync - var branchesToSync: [(branch: Branch, bot: Bot)] = [] + var branchesToSync: [(branch: BranchType, bot: Bot)] = [] //PRs that don't have a bot yet, to create - var prBotsToCreate: [PullRequest] = [] + var prBotsToCreate: [PullRequestType] = [] //branches that don't have a bot yet, to create - var branchBotsToCreate: [Branch] = [] + var branchBotsToCreate: [BranchType] = [] //make sure every PR has a bot for pr in prs { @@ -246,7 +258,7 @@ extension HDGitHubXCBotSyncer { return (prsToSync, prBotsToCreate, branchesToSync, branchBotsToCreate, botsToDelete) } - public func createSyncPairsFrom(repo repo: Repo, botActions: BotActions) -> [SyncPair] { + public func createSyncPairsFrom(repo repo: RepoType, botActions: BotActions) -> [SyncPair] { //create sync pairs for each action needed let syncPRBotSyncPairs = botActions.prsToSync.map({ diff --git a/BuildaKit/SyncerManager.swift b/BuildaKit/SyncerManager.swift index 62046e0..7ae5799 100644 --- a/BuildaKit/SyncerManager.swift +++ b/BuildaKit/SyncerManager.swift @@ -21,14 +21,14 @@ public class SyncerManager { public let factory: SyncerFactoryType public let loginItem: LoginItem - public let syncersProducer: SignalProducer<[HDGitHubXCBotSyncer], NoError> + public let syncersProducer: SignalProducer<[StandardSyncer], NoError> public let projectsProducer: SignalProducer<[Project], NoError> public let serversProducer: SignalProducer<[XcodeServer], NoError> public let buildTemplatesProducer: SignalProducer<[BuildTemplate], NoError> public let triggerProducer: SignalProducer<[Trigger], NoError> - public var syncers: [HDGitHubXCBotSyncer] + public var syncers: [StandardSyncer] private var configTriplets: SignalProducer<[ConfigTriplet], NoError> private var heartbeatManager: HeartbeatManager! @@ -90,9 +90,9 @@ public class SyncerManager { } } - public func syncerWithRef(ref: RefType) -> SignalProducer { + public func syncerWithRef(ref: RefType) -> SignalProducer { - return self.syncersProducer.map { allSyncers -> HDGitHubXCBotSyncer? in + return self.syncersProducer.map { allSyncers -> StandardSyncer? in return allSyncers.filter { $0.config.value.id == ref }.first } } diff --git a/BuildaKit/SyncerProducerFactory.swift b/BuildaKit/SyncerProducerFactory.swift index 222782c..2de4478 100644 --- a/BuildaKit/SyncerProducerFactory.swift +++ b/BuildaKit/SyncerProducerFactory.swift @@ -68,9 +68,9 @@ class SyncerProducerFactory { return triplets } - static func createSyncersProducer(factory: SyncerFactoryType, triplets: SignalProducer<[ConfigTriplet], NoError>) -> SignalProducer<[HDGitHubXCBotSyncer], NoError> { + static func createSyncersProducer(factory: SyncerFactoryType, triplets: SignalProducer<[ConfigTriplet], NoError>) -> SignalProducer<[StandardSyncer], NoError> { - let syncers = triplets.map { (tripletArray: [ConfigTriplet]) -> [HDGitHubXCBotSyncer] in + let syncers = triplets.map { (tripletArray: [ConfigTriplet]) -> [StandardSyncer] in return factory.createSyncers(tripletArray) } return syncers diff --git a/BuildaKit/WorkspaceMetadata.swift b/BuildaKit/WorkspaceMetadata.swift index 9f1fe08..90e2ecb 100644 --- a/BuildaKit/WorkspaceMetadata.swift +++ b/BuildaKit/WorkspaceMetadata.swift @@ -8,6 +8,7 @@ import Foundation import BuildaUtils +import BuildaGitServer public enum CheckoutType: String { case SSH = "SSH" @@ -23,6 +24,7 @@ public struct WorkspaceMetadata { public let projectWCCIdentifier: String public let projectWCCName: String public let projectURL: NSURL + public let service: GitService public let checkoutType: CheckoutType init(projectName: String?, projectPath: String?, projectWCCIdentifier: String?, projectWCCName: String?, projectURLString: String?) throws { @@ -33,7 +35,7 @@ public struct WorkspaceMetadata { guard let projectWCCIdentifier = projectWCCIdentifier else { throw errorForMissingKey("Project WCC Identifier") } guard let projectWCCName = projectWCCName else { throw errorForMissingKey("Project WCC Name") } guard let projectURLString = projectURLString else { throw errorForMissingKey("Project URL") } - guard let checkoutType = WorkspaceMetadata.parseCheckoutType(projectURLString) else { + guard let (checkoutType, service) = WorkspaceMetadata.parse(projectURLString) else { let allowedString = [CheckoutType.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 @@ -53,6 +55,7 @@ public struct WorkspaceMetadata { self.projectWCCName = projectWCCName self.projectURL = projectURL self.checkoutType = checkoutType + self.service = service } func duplicateWithForkURL(forkUrlString: String?) throws -> WorkspaceMetadata { @@ -62,7 +65,7 @@ public struct WorkspaceMetadata { extension WorkspaceMetadata { - internal static func parseCheckoutType(projectURLString: String) -> CheckoutType? { + internal static func parse(projectURLString: String) -> (CheckoutType, GitService)? { var urlString = projectURLString @@ -76,7 +79,9 @@ extension WorkspaceMetadata { let scheme = NSURL(string: urlString)!.scheme switch scheme { case "github.com": - return CheckoutType.SSH + return (CheckoutType.SSH, .GitHub) + case "bitbucket.org": + return (CheckoutType.SSH, .BitBucket) case "https": if urlString.hasSuffix(".git") { diff --git a/BuildaKit/XcodeServerSyncerUtils.swift b/BuildaKit/XcodeServerSyncerUtils.swift index d97f8d7..1f482b2 100644 --- a/BuildaKit/XcodeServerSyncerUtils.swift +++ b/BuildaKit/XcodeServerSyncerUtils.swift @@ -13,7 +13,7 @@ import BuildaUtils public class XcodeServerSyncerUtils { - public class func createBotFromBuildTemplate(botName: String, syncer: HDGitHubXCBotSyncer, template: BuildTemplate, project: Project, branch: String, scheduleOverride: BotSchedule?, xcodeServer: XcodeServer, completion: (bot: Bot?, error: NSError?) -> ()) { + public class func createBotFromBuildTemplate(botName: String, syncer: StandardSyncer, template: BuildTemplate, project: Project, branch: String, scheduleOverride: BotSchedule?, xcodeServer: XcodeServer, completion: (bot: Bot?, error: NSError?) -> ()) { //pull info from template let schemeName = template.scheme diff --git a/BuildaKitTests/SummaryBuilderTests.swift b/BuildaKitTests/GitHubSummaryBuilderTests.swift similarity index 88% rename from BuildaKitTests/SummaryBuilderTests.swift rename to BuildaKitTests/GitHubSummaryBuilderTests.swift index 12e6336..1c7d96f 100644 --- a/BuildaKitTests/SummaryBuilderTests.swift +++ b/BuildaKitTests/GitHubSummaryBuilderTests.swift @@ -12,7 +12,7 @@ import BuildaGitServer @testable import BuildaKit import Nimble -class SummaryBuilderTests: XCTestCase { +class GitHubSummaryBuilderTests: XCTestCase { //MARK: utils @@ -34,11 +34,12 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary() let integration = self.integration(.Succeeded, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildPassing(integration) let exp_comment = "Result of Integration 15\n---\n*Duration*: 28 seconds\n*Result*: **Perfect build!** :+1:" let exp_status = "Build passed!" - let exp_state = Status.State.Success + let exp_state = BuildState.Success expect(result.comment) == exp_comment expect(result.status.description) == exp_status expect(result.status.state) == exp_state @@ -50,12 +51,13 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary() let integration = self.integration(.Succeeded, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() summary.linkBuilder = self.linkBuilder() let result = summary.buildPassing(integration) let exp_comment = "Result of [Integration 15](https://link/to/d3884f0ab7df9c699bc81405f4045ec6)\n---\n*Duration*: 28 seconds\n*Result*: **Perfect build!** :+1:" let exp_status = "Build passed!" - let exp_state = Status.State.Success + let exp_state = BuildState.Success let exp_link = "https://link/to/d3884f0ab7df9c699bc81405f4045ec6" expect(result.comment) == exp_comment expect(result.status.description) == exp_status @@ -68,11 +70,12 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary(codeCoveragePercentage: 12) let integration = self.integration(.Succeeded, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildPassing(integration) let exp_comment = "Result of Integration 15\n---\n*Duration*: 28 seconds\n*Result*: **Perfect build!** :+1:\n*Test Coverage*: 12%" let exp_status = "Build passed!" - let exp_state = Status.State.Success + let exp_state = BuildState.Success expect(result.comment) == exp_comment expect(result.status.description) == exp_status expect(result.status.state) == exp_state @@ -84,11 +87,12 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary(testsCount: 99, codeCoveragePercentage: 12) let integration = self.integration(.Succeeded, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildPassing(integration) let exp_comment = "Result of Integration 15\n---\n*Duration*: 28 seconds\n*Result*: **Perfect build!** All 99 tests passed. :+1:\n*Test Coverage*: 12%" let exp_status = "Build passed!" - let exp_state = Status.State.Success + let exp_state = BuildState.Success expect(result.comment) == exp_comment expect(result.status.description) == exp_status expect(result.status.state) == exp_state @@ -99,11 +103,12 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary(testsCount: 99, warningCount: 2, codeCoveragePercentage: 12) let integration = self.integration(.Warnings, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildPassing(integration) let exp_comment = "Result of Integration 15\n---\n*Duration*: 28 seconds\n*Result*: All 99 tests passed, but please **fix 2 warnings**.\n*Test Coverage*: 12%" let exp_status = "Build passed!" - let exp_state = Status.State.Success + let exp_state = BuildState.Success expect(result.comment) == exp_comment expect(result.status.description) == exp_status expect(result.status.state) == exp_state @@ -114,11 +119,12 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary(analyzerWarningCount: 3, testsCount: 99, codeCoveragePercentage: 12) let integration = self.integration(.AnalyzerWarnings, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildPassing(integration) let exp_comment = "Result of Integration 15\n---\n*Duration*: 28 seconds\n*Result*: All 99 tests passed, but please **fix 3 analyzer warnings**.\n*Test Coverage*: 12%" let exp_status = "Build passed!" - let exp_state = Status.State.Success + let exp_state = BuildState.Success expect(result.comment) == exp_comment expect(result.status.description) == exp_status expect(result.status.state) == exp_state @@ -130,11 +136,12 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary(testFailureCount: 1, testsCount: 99) let integration = self.integration(.TestFailures, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildFailingTests(integration) let exp_comment = "Result of Integration 15\n---\n*Duration*: 28 seconds\n*Result*: **Build failed 1 test** out of 99" let exp_status = "Build failed tests!" - let exp_state = Status.State.Failure + let exp_state = BuildState.Failure expect(result.comment) == exp_comment expect(result.status.description) == exp_status expect(result.status.state) == exp_state @@ -145,11 +152,12 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary(errorCount: 4) let integration = self.integration(.BuildErrors, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildErrorredIntegration(integration) let exp_comment = "Result of Integration 15\n---\n*Duration*: 28 seconds\n*Result*: **4 errors, failing state: build-errors**" let exp_status = "Build error!" - let exp_state = Status.State.Error + let exp_state = BuildState.Error expect(result.comment) == exp_comment expect(result.status.description) == exp_status expect(result.status.state) == exp_state @@ -160,11 +168,12 @@ class SummaryBuilderTests: XCTestCase { let buildResultSummary = MockBuildResultSummary() let integration = self.integration(.Canceled, buildResultSummary: buildResultSummary) let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildCanceledIntegration(integration) let exp_comment = "Result of Integration 15\n---\n*Duration*: 28 seconds\nBuild was **manually canceled**." let exp_status = "Build canceled!" - let exp_state = Status.State.Error + let exp_state = BuildState.Error expect(result.comment) == exp_comment expect(result.status.description) == exp_status expect(result.status.state) == exp_state @@ -173,9 +182,10 @@ class SummaryBuilderTests: XCTestCase { func testEmpty() { let summary = SummaryBuilder() + summary.statusCreator = MockGitHubServer() let result = summary.buildEmptyIntegration() - let exp_state = Status.State.NoState + let exp_state = BuildState.NoState expect(result.comment).to(beNil()) expect(result.status.description).to(beNil()) expect(result.status.state) == exp_state diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json b/BuildaKitTests/Migration/Buildasaur-format-2-example2/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json new file mode 100644 index 0000000..c0064b6 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json @@ -0,0 +1,26 @@ +{ + "id" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", + "project_name" : "Buildasaur", + "schedule" : { + "weeklyScheduleDay" : 0, + "periodicScheduleInterval" : 0, + "hourOfIntegration" : 0, + "minutesAfterHourToIntegrate" : 0, + "scheduleType" : 3 + }, + "triggers" : [ + "E8F5285A-A262-4630-AF7B-236772B75760", + "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" + ], + "should_analyze" : true, + "platform_type" : "com.apple.platform.macosx", + "scheme" : "Buildasaur", + "device_filter" : 0, + "cleaning_policy" : 0, + "testing_devices" : [ + + ], + "should_archive" : false, + "name" : "Buildasaur PR", + "should_test" : true +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json b/BuildaKitTests/Migration/Buildasaur-format-2-example2/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json new file mode 100644 index 0000000..8d6a805 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json @@ -0,0 +1,26 @@ +{ + "id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", + "project_name" : "Buildasaur-Tester", + "schedule" : { + "weeklyScheduleDay" : 0, + "periodicScheduleInterval" : 0, + "hourOfIntegration" : 0, + "minutesAfterHourToIntegrate" : 0, + "scheduleType" : 3 + }, + "triggers" : [ + "E8F5285A-A262-4630-AF7B-236772B75760", + "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" + ], + "should_analyze" : true, + "platform_type" : "com.apple.platform.iphoneos", + "scheme" : "Buildasaur-Tester", + "device_filter" : 0, + "cleaning_policy" : 0, + "testing_devices" : [ + + ], + "should_archive" : false, + "name" : "BuildaTest PR", + "should_test" : true +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/Builda.log b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Builda.log new file mode 100644 index 0000000..253ec35 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Builda.log @@ -0,0 +1,89 @@ +* +* +* + ____ _ _ _ +| _ \ (_) | | | +| |_) |_ _ _| | __| | __ _ ___ __ _ _ _ _ __ +| _ <| | | | | |/ _` |/ _` / __|/ _` | | | | '__| +| |_) | |_| | | | (_| | (_| \__ \ (_| | |_| | | +|____/ \__,_|_|_|\__,_|\__,_|___/\__,_|\__,_|_| + +Buildasaur 0.5.1 launched at 2015-10-12 13:57:46 +0000. +* +* +* + +[INFO]: Will send anonymous heartbeat. To opt out add `"heartbeat_opt_out" = true` to ~/Library/Application Support/Buildasaur/Config.json +[INFO]: Sending heartbeat event ["event_type": launch] +[INFO]: Sending heartbeat event ["event_type": heartbeat, "uptime": 0.03319501876831055, "running_syncers": 0] +[INFO]: Project: file:///Users/honzadvorsky/Documents/Buildasaur-Tester/Buildasaur-Tester.xcodeproj +[VERBOSE]: Finished fetching devices +[INFO]: Key: Optional(file:///Users/honzadvorsky/.ssh/id_rsa.pub) +[INFO]: Key: Optional(file:///Users/honzadvorsky/.ssh/id_rsa) + +------------------------------------ + +[INFO]: Sync starting at 2015-10-12 14:00:27 +0000 +[VERBOSE]: Resolving prs: + PR 9: test change [czechboy0-patch-6 -> master] + PR 8: Update README.md PR2 [czechboy0-patch-3 -> czechboy0-patch-2] + PR 6: Update ViewController.swift [czechboy0-patch-4 -> master] + PR 4: Update README.md [czechboy0-patch-2 -> master] +and branches: + +and bots: + Bot Buildasaur Bot + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #6 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #9 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #4 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #8 + Bot BuildaBot [czechboy0/XcodeServerSDK] PR #117 + Bot BuildaBot [czechboy0/XcodeServerSDK] PR #106 + Bot BuildaBot [czechboy0/Buildasaur] PR #138 + Bot BuildaBot [czechboy0/XcodeServerSDK] |-> master + Bot BuildaBot [czechboy0/Buildasaur] |-> master + Bot Buildasaur-TestProject-iOS Bot +[VERBOSE]: SyncPair PR (9:czechboy0-patch-6) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #9) finished sync after 0.388 seconds. +[VERBOSE]: SyncPair PR (6:czechboy0-patch-4) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #6) finished sync after 0.702 seconds. +[VERBOSE]: SyncPair PR (4:czechboy0-patch-2) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #4) finished sync after 0.718 seconds. +[VERBOSE]: SyncPair PR (8:czechboy0-patch-3) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #8) finished sync after 1.156 seconds. +[INFO]: GitHub Rate Limit: count: 0/5000, renews in 3598 seconds, rate: 0.0/1.38, using 0.0% of the allowed request rate. +[INFO]: Sync finished successfully at 2015-10-12 14:00:29 +0000, took 1.59 seconds. + +------------------------------------ + +[INFO]: Sync starting at 2015-10-12 14:00:34 +0000 +[VERBOSE]: Resolving prs: + PR 9: test change [czechboy0-patch-6 -> master] + PR 8: Update README.md PR2 [czechboy0-patch-3 -> czechboy0-patch-2] + PR 6: Update ViewController.swift [czechboy0-patch-4 -> master] + PR 4: Update README.md [czechboy0-patch-2 -> master] +and branches: + Branch [almost-master:5b1d734f0170583b54fa749ff9bc47670abd66ad] + Branch [czechboy0-patch-1:ddb1e4840e35a611c2627168d8330ddf80c569d9] + Branch [czechboy0-patch-2:26cf7e9a027579f057aceaa9b09625317d31efde] + Branch [czechboy0-patch-3:ef2d5e2e0755bdff3945134e9a160d86a0698fe2] + Branch [czechboy0-patch-4:603e18851f66ee66a1223ea65e5f1e18e4353664] + Branch [czechboy0-patch-5:c1da8cd5cc61c62f8700766314ba26c7f03ea9ab] + Branch [czechboy0-patch-6:2ab73752fe7ed2d8f67187d7ba0552126a158de1] + Branch [master:510352edd9cdabd45f608e4327a8dd48ac517230] +and bots: + Bot Buildasaur Bot + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #6 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #9 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #4 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #8 + Bot BuildaBot [czechboy0/XcodeServerSDK] PR #117 + Bot BuildaBot [czechboy0/XcodeServerSDK] PR #106 + Bot BuildaBot [czechboy0/Buildasaur] PR #138 + Bot BuildaBot [czechboy0/XcodeServerSDK] |-> master + Bot BuildaBot [czechboy0/Buildasaur] |-> master + Bot Buildasaur-TestProject-iOS Bot +[VERBOSE]: SyncPair PR (4:czechboy0-patch-2) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #4) finished sync after 0.068 seconds. +[VERBOSE]: SyncPair PR (6:czechboy0-patch-4) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #6) finished sync after 0.165 seconds. +[VERBOSE]: SyncPair PR (8:czechboy0-patch-3) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #8) finished sync after 0.186 seconds. +[VERBOSE]: SyncPair PR (9:czechboy0-patch-6) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #9) finished sync after 0.191 seconds. +[INFO]: Successfully created bot BuildaBot [czechboy0/Buildasaur-Tester] |-> master +[VERBOSE]: SyncPair Branch (master) + No Bot finished sync after 3.97 seconds. +[INFO]: GitHub Rate Limit: count: 0/5000, renews in 3593 seconds, rate: 0.0/1.38, using 0.0% of the allowed request rate. +[INFO]: Sync finished successfully at 2015-10-12 14:00:38 +0000, took 4.027 seconds. diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/Config.json b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Config.json new file mode 100644 index 0000000..2a71818 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Config.json @@ -0,0 +1,3 @@ +{ + "persistence_version" : 2 +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/Projects.json b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Projects.json new file mode 100644 index 0000000..ad48e69 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Projects.json @@ -0,0 +1,18 @@ +[ + { + "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", + "id" : "4E8E7708-01FB-448A-B929-A54887CC5857", + "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", + "github_token" : "example_token-123456786543456765432", + "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa", + "ssh_passphrase": "my_passphrase_much_secret" + }, + { + "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", + "id" : "D316F062-7FEC-497A-B20E-6776AA413009", + "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur\/Buildasaur.xcodeproj", + "github_token" : "example_token-44446786543456765432", + "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa", + "ssh_passphrase": "my_passphrase_much_secret_2" + } +] \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/ServerConfigs.json b/BuildaKitTests/Migration/Buildasaur-format-2-example2/ServerConfigs.json new file mode 100644 index 0000000..f222a99 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/ServerConfigs.json @@ -0,0 +1,14 @@ +[ + { + "password" : "SuperSecretPa55word", + "id" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", + "host" : "https:\/\/127.0.0.1", + "user" : "testadmin1" + }, + { + "password" : "SuperSecretPa55word77878787", + "id" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", + "host" : "https:\/\/localhost", + "user" : "testadmin8" + } +] \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/Syncers.json b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Syncers.json new file mode 100644 index 0000000..c75f4f4 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Syncers.json @@ -0,0 +1,26 @@ +[ + { + "preferred_template_ref" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", + "id" : "564C267D-FF06-4008-9EF6-66B3AC1A3BDE", + "sync_interval" : 19, + "server_ref" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", + "wait_for_lttm" : false, + "watched_branches" : [ + "master" + ], + "project_ref" : "4E8E7708-01FB-448A-B929-A54887CC5857", + "post_status_comments" : false + }, + { + "preferred_template_ref" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", + "id" : "B3A35C28-2176-4D88-8F60-5C769AEDBB2E", + "sync_interval" : 15, + "server_ref" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", + "wait_for_lttm" : false, + "watched_branches" : [ + "master" + ], + "project_ref" : "D316F062-7FEC-497A-B20E-6776AA413009", + "post_status_comments" : false + } +] \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json new file mode 100644 index 0000000..b480ff1 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json @@ -0,0 +1,25 @@ +{ + "phase" : 2, + "id" : "4E66D0D5-D5CC-417E-A40E-73B513CE4E10", + "scriptBody" : "", + "conditions" : { + "status" : 2, + "onWarnings" : true, + "onBuildErrors" : true, + "onInternalErrors" : true, + "onAnalyzerWarnings" : true, + "onFailingTests" : true, + "onSuccess" : true + }, + "type" : 2, + "name" : "Postbuild Email", + "emailConfiguration" : { + "includeCommitMessages" : true, + "additionalRecipients" : [ + "h@d.com", + "yo@ma.lo" + ], + "emailCommitters" : false, + "includeIssueDetails" : true + } +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-2-example2/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json new file mode 100644 index 0000000..5c263b7 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-2-example2/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json @@ -0,0 +1,7 @@ +{ + "phase" : 1, + "scriptBody" : "echo \"hello\"\n", + "id" : "E8F5285A-A262-4630-AF7B-236772B75760", + "type" : 1, + "name" : "Prebuild Script" +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json b/BuildaKitTests/Migration/Buildasaur-format-3-example1/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json new file mode 100644 index 0000000..c0064b6 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json @@ -0,0 +1,26 @@ +{ + "id" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", + "project_name" : "Buildasaur", + "schedule" : { + "weeklyScheduleDay" : 0, + "periodicScheduleInterval" : 0, + "hourOfIntegration" : 0, + "minutesAfterHourToIntegrate" : 0, + "scheduleType" : 3 + }, + "triggers" : [ + "E8F5285A-A262-4630-AF7B-236772B75760", + "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" + ], + "should_analyze" : true, + "platform_type" : "com.apple.platform.macosx", + "scheme" : "Buildasaur", + "device_filter" : 0, + "cleaning_policy" : 0, + "testing_devices" : [ + + ], + "should_archive" : false, + "name" : "Buildasaur PR", + "should_test" : true +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json b/BuildaKitTests/Migration/Buildasaur-format-3-example1/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json new file mode 100644 index 0000000..8d6a805 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json @@ -0,0 +1,26 @@ +{ + "id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", + "project_name" : "Buildasaur-Tester", + "schedule" : { + "weeklyScheduleDay" : 0, + "periodicScheduleInterval" : 0, + "hourOfIntegration" : 0, + "minutesAfterHourToIntegrate" : 0, + "scheduleType" : 3 + }, + "triggers" : [ + "E8F5285A-A262-4630-AF7B-236772B75760", + "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" + ], + "should_analyze" : true, + "platform_type" : "com.apple.platform.iphoneos", + "scheme" : "Buildasaur-Tester", + "device_filter" : 0, + "cleaning_policy" : 0, + "testing_devices" : [ + + ], + "should_archive" : false, + "name" : "BuildaTest PR", + "should_test" : true +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/Config.json b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Config.json new file mode 100644 index 0000000..906c841 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Config.json @@ -0,0 +1,3 @@ +{ + "persistence_version" : 3 +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/Logs/Builda.log b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Logs/Builda.log new file mode 100644 index 0000000..253ec35 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Logs/Builda.log @@ -0,0 +1,89 @@ +* +* +* + ____ _ _ _ +| _ \ (_) | | | +| |_) |_ _ _| | __| | __ _ ___ __ _ _ _ _ __ +| _ <| | | | | |/ _` |/ _` / __|/ _` | | | | '__| +| |_) | |_| | | | (_| | (_| \__ \ (_| | |_| | | +|____/ \__,_|_|_|\__,_|\__,_|___/\__,_|\__,_|_| + +Buildasaur 0.5.1 launched at 2015-10-12 13:57:46 +0000. +* +* +* + +[INFO]: Will send anonymous heartbeat. To opt out add `"heartbeat_opt_out" = true` to ~/Library/Application Support/Buildasaur/Config.json +[INFO]: Sending heartbeat event ["event_type": launch] +[INFO]: Sending heartbeat event ["event_type": heartbeat, "uptime": 0.03319501876831055, "running_syncers": 0] +[INFO]: Project: file:///Users/honzadvorsky/Documents/Buildasaur-Tester/Buildasaur-Tester.xcodeproj +[VERBOSE]: Finished fetching devices +[INFO]: Key: Optional(file:///Users/honzadvorsky/.ssh/id_rsa.pub) +[INFO]: Key: Optional(file:///Users/honzadvorsky/.ssh/id_rsa) + +------------------------------------ + +[INFO]: Sync starting at 2015-10-12 14:00:27 +0000 +[VERBOSE]: Resolving prs: + PR 9: test change [czechboy0-patch-6 -> master] + PR 8: Update README.md PR2 [czechboy0-patch-3 -> czechboy0-patch-2] + PR 6: Update ViewController.swift [czechboy0-patch-4 -> master] + PR 4: Update README.md [czechboy0-patch-2 -> master] +and branches: + +and bots: + Bot Buildasaur Bot + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #6 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #9 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #4 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #8 + Bot BuildaBot [czechboy0/XcodeServerSDK] PR #117 + Bot BuildaBot [czechboy0/XcodeServerSDK] PR #106 + Bot BuildaBot [czechboy0/Buildasaur] PR #138 + Bot BuildaBot [czechboy0/XcodeServerSDK] |-> master + Bot BuildaBot [czechboy0/Buildasaur] |-> master + Bot Buildasaur-TestProject-iOS Bot +[VERBOSE]: SyncPair PR (9:czechboy0-patch-6) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #9) finished sync after 0.388 seconds. +[VERBOSE]: SyncPair PR (6:czechboy0-patch-4) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #6) finished sync after 0.702 seconds. +[VERBOSE]: SyncPair PR (4:czechboy0-patch-2) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #4) finished sync after 0.718 seconds. +[VERBOSE]: SyncPair PR (8:czechboy0-patch-3) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #8) finished sync after 1.156 seconds. +[INFO]: GitHub Rate Limit: count: 0/5000, renews in 3598 seconds, rate: 0.0/1.38, using 0.0% of the allowed request rate. +[INFO]: Sync finished successfully at 2015-10-12 14:00:29 +0000, took 1.59 seconds. + +------------------------------------ + +[INFO]: Sync starting at 2015-10-12 14:00:34 +0000 +[VERBOSE]: Resolving prs: + PR 9: test change [czechboy0-patch-6 -> master] + PR 8: Update README.md PR2 [czechboy0-patch-3 -> czechboy0-patch-2] + PR 6: Update ViewController.swift [czechboy0-patch-4 -> master] + PR 4: Update README.md [czechboy0-patch-2 -> master] +and branches: + Branch [almost-master:5b1d734f0170583b54fa749ff9bc47670abd66ad] + Branch [czechboy0-patch-1:ddb1e4840e35a611c2627168d8330ddf80c569d9] + Branch [czechboy0-patch-2:26cf7e9a027579f057aceaa9b09625317d31efde] + Branch [czechboy0-patch-3:ef2d5e2e0755bdff3945134e9a160d86a0698fe2] + Branch [czechboy0-patch-4:603e18851f66ee66a1223ea65e5f1e18e4353664] + Branch [czechboy0-patch-5:c1da8cd5cc61c62f8700766314ba26c7f03ea9ab] + Branch [czechboy0-patch-6:2ab73752fe7ed2d8f67187d7ba0552126a158de1] + Branch [master:510352edd9cdabd45f608e4327a8dd48ac517230] +and bots: + Bot Buildasaur Bot + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #6 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #9 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #4 + Bot BuildaBot [czechboy0/Buildasaur-Tester] PR #8 + Bot BuildaBot [czechboy0/XcodeServerSDK] PR #117 + Bot BuildaBot [czechboy0/XcodeServerSDK] PR #106 + Bot BuildaBot [czechboy0/Buildasaur] PR #138 + Bot BuildaBot [czechboy0/XcodeServerSDK] |-> master + Bot BuildaBot [czechboy0/Buildasaur] |-> master + Bot Buildasaur-TestProject-iOS Bot +[VERBOSE]: SyncPair PR (4:czechboy0-patch-2) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #4) finished sync after 0.068 seconds. +[VERBOSE]: SyncPair PR (6:czechboy0-patch-4) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #6) finished sync after 0.165 seconds. +[VERBOSE]: SyncPair PR (8:czechboy0-patch-3) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #8) finished sync after 0.186 seconds. +[VERBOSE]: SyncPair PR (9:czechboy0-patch-6) + Bot (BuildaBot [czechboy0/Buildasaur-Tester] PR #9) finished sync after 0.191 seconds. +[INFO]: Successfully created bot BuildaBot [czechboy0/Buildasaur-Tester] |-> master +[VERBOSE]: SyncPair Branch (master) + No Bot finished sync after 3.97 seconds. +[INFO]: GitHub Rate Limit: count: 0/5000, renews in 3593 seconds, rate: 0.0/1.38, using 0.0% of the allowed request rate. +[INFO]: Sync finished successfully at 2015-10-12 14:00:38 +0000, took 4.027 seconds. diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/Projects.json b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Projects.json new file mode 100644 index 0000000..a82d7e1 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Projects.json @@ -0,0 +1,14 @@ +[ + { + "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", + "id" : "4E8E7708-01FB-448A-B929-A54887CC5857", + "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", + "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa" + }, + { + "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", + "id" : "D316F062-7FEC-497A-B20E-6776AA413009", + "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur\/Buildasaur.xcodeproj", + "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa" + } +] \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/ServerConfigs.json b/BuildaKitTests/Migration/Buildasaur-format-3-example1/ServerConfigs.json new file mode 100644 index 0000000..caa2de8 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/ServerConfigs.json @@ -0,0 +1,12 @@ +[ + { + "id" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", + "host" : "https:\/\/127.0.0.1", + "user" : "testadmin1" + }, + { + "id" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", + "host" : "https:\/\/localhost", + "user" : "testadmin8" + } +] \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/Syncers.json b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Syncers.json new file mode 100644 index 0000000..c75f4f4 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Syncers.json @@ -0,0 +1,26 @@ +[ + { + "preferred_template_ref" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", + "id" : "564C267D-FF06-4008-9EF6-66B3AC1A3BDE", + "sync_interval" : 19, + "server_ref" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", + "wait_for_lttm" : false, + "watched_branches" : [ + "master" + ], + "project_ref" : "4E8E7708-01FB-448A-B929-A54887CC5857", + "post_status_comments" : false + }, + { + "preferred_template_ref" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", + "id" : "B3A35C28-2176-4D88-8F60-5C769AEDBB2E", + "sync_interval" : 15, + "server_ref" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", + "wait_for_lttm" : false, + "watched_branches" : [ + "master" + ], + "project_ref" : "D316F062-7FEC-497A-B20E-6776AA413009", + "post_status_comments" : false + } +] \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json new file mode 100644 index 0000000..b480ff1 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json @@ -0,0 +1,25 @@ +{ + "phase" : 2, + "id" : "4E66D0D5-D5CC-417E-A40E-73B513CE4E10", + "scriptBody" : "", + "conditions" : { + "status" : 2, + "onWarnings" : true, + "onBuildErrors" : true, + "onInternalErrors" : true, + "onAnalyzerWarnings" : true, + "onFailingTests" : true, + "onSuccess" : true + }, + "type" : 2, + "name" : "Postbuild Email", + "emailConfiguration" : { + "includeCommitMessages" : true, + "additionalRecipients" : [ + "h@d.com", + "yo@ma.lo" + ], + "emailCommitters" : false, + "includeIssueDetails" : true + } +} \ No newline at end of file diff --git a/BuildaKitTests/Migration/Buildasaur-format-3-example1/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json new file mode 100644 index 0000000..5c263b7 --- /dev/null +++ b/BuildaKitTests/Migration/Buildasaur-format-3-example1/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json @@ -0,0 +1,7 @@ +{ + "phase" : 1, + "scriptBody" : "echo \"hello\"\n", + "id" : "E8F5285A-A262-4630-AF7B-236772B75760", + "type" : 1, + "name" : "Prebuild Script" +} \ No newline at end of file diff --git a/BuildaKitTests/MigrationTests.swift b/BuildaKitTests/MigrationTests.swift index 8ffbd13..e856678 100644 --- a/BuildaKitTests/MigrationTests.swift +++ b/BuildaKitTests/MigrationTests.swift @@ -119,6 +119,25 @@ class MigrationTests: XCTestCase { } } + func testMigration_v2_v3() { + + let readingURL = self.resourceURLFromTestBundle("Buildasaur-format-2-example2") + let writingURL = self.writingURL("v2-v3") + let expectedURL = self.resourceURLFromTestBundle("Buildasaur-format-3-example1") + + let fileManager = NSFileManager.defaultManager() + + let persistence = Persistence(readingFolder: readingURL, writingFolder: writingURL, fileManager: fileManager) + let migrator = Migrator_v2_v3(persistence: persistence) + + do { + try migrator.attemptMigration() + try self.ensureEqualHierarchies(persistence, urlExpected: expectedURL, urlReal: writingURL) + } catch { + fail("\(error)") + } + } + func testPersistenceSetter() { let tmp = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) diff --git a/BuildaKitTests/Mocks.swift b/BuildaKitTests/Mocks.swift index 84b3752..4e1211f 100644 --- a/BuildaKitTests/Mocks.swift +++ b/BuildaKitTests/Mocks.swift @@ -8,7 +8,7 @@ import Foundation import BuildaUtils -import BuildaGitServer +@testable import BuildaGitServer import Buildasaur import XcodeServerSDK import BuildaKit @@ -22,7 +22,7 @@ class MockXcodeServer: XcodeServer { class MockGitHubServer: GitHubServer { init() { - super.init(endpoints: GitHubEndpoints(baseURL: "", token: "")) + super.init(endpoints: GitHubEndpoints(baseURL: "", auth: nil)) } } @@ -45,7 +45,7 @@ class MockTemplate { } } -class MockRepo: Repo { +class MockRepo: GitHubRepo { class func mockDictionary() -> NSDictionary { return [ @@ -65,7 +65,7 @@ class MockRepo: Repo { } } -class MockBranch: Branch { +class MockBranch: GitHubBranch { class func mockDictionary(name: String = "master", sha: String = "1234f") -> NSDictionary { return [ @@ -85,7 +85,7 @@ class MockBranch: Branch { } } -class MockPullRequestBranch: PullRequestBranch { +class MockPullRequestBranch: GitHubPullRequestBranch { class func mockDictionary(ref: String = "mock_ref", sha: String = "1234f") -> NSDictionary { return [ @@ -104,7 +104,7 @@ class MockPullRequestBranch: PullRequestBranch { } } -class MockIssue: Issue { +class MockIssue: GitHubIssue { class func mockDictionary(number: Int = 1, body: String = "body", title: String = "title") -> NSDictionary { return [ @@ -123,7 +123,15 @@ class MockIssue: Issue { } } -class MockPullRequest: PullRequest { +class MockBuildStatusCreator: BuildStatusCreator { + func createStatusFromState(state: BuildState, description: String?, targetUrl: String?) -> StatusType { + return GitHubStatus(state: GitHubStatus.GitHubState.fromBuildState(state), description: "Things happened", targetUrl: "http://hello.world", context: "Buildasaur") + } + + init() { } +} + +class MockPullRequest: GitHubPullRequest { class func mockDictionary(number: Int, title: String, head: NSDictionary, base: NSDictionary) -> NSDictionary { let dict = MockIssue.mockDictionary(number, body: "body", title: title).mutableCopy() as! NSMutableDictionary diff --git a/BuildaKitTests/SyncPair_PR_Bot_Tests.swift b/BuildaKitTests/SyncPair_PR_Bot_Tests.swift index 2ece2db..2aae723 100644 --- a/BuildaKitTests/SyncPair_PR_Bot_Tests.swift +++ b/BuildaKitTests/SyncPair_PR_Bot_Tests.swift @@ -27,50 +27,51 @@ func XCTBAssertNotNil(@autoclosure expression: () -> T?, message: String = " class SyncPair_PR_Bot_Tests: XCTestCase { - func mockedPRAndBotAndCommit() -> (PullRequest, Bot, String) { + func mockedPRAndBotAndCommit() -> (PullRequestType, Bot, String, BuildStatusCreator) { - let pr = MockPullRequest(number: 1, title: "Awesomified The Engine") + let pr: PullRequestType = MockPullRequest(number: 1, title: "Awesomified The Engine") let bot = MockBot(name: "BuildaBot [me/Repo] PR #1") - let commit = pr.head.sha + let commit = pr.headCommitSHA + let buildStatusCreator = MockBuildStatusCreator() - return (pr, bot, commit) + return (pr, bot, commit, buildStatusCreator) } func testNoIntegrationsYet() { - let (pr, bot, commit) = self.mockedPRAndBotAndCommit() + let (pr, bot, commit, statusCreator) = self.mockedPRAndBotAndCommit() let integrations = [Integration]() - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", buildStatusCreator: statusCreator, integrations: integrations) XCTAssertEqual(actions.integrationsToCancel?.count ?? 0, 0) - XCTBAssertNil(actions.githubStatusToSet) + XCTBAssertNil(actions.statusToSet) XCTAssertNotNil(actions.startNewIntegrationBot) } func testFirstIntegrationPending() { - let (pr, bot, commit) = self.mockedPRAndBotAndCommit() + let (pr, bot, commit, statusCreator) = self.mockedPRAndBotAndCommit() let integrations = [ MockIntegration(number: 1, step: Integration.Step.Pending) ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", buildStatusCreator: statusCreator, integrations: integrations) XCTAssertEqual(actions.integrationsToCancel?.count ?? 0, 0) XCTAssertNil(actions.startNewIntegrationBot) - XCTBAssertNotNil(actions.githubStatusToSet) - XCTAssertEqual(actions.githubStatusToSet!.status.status.state, Status.State.Pending) + XCTBAssertNotNil(actions.statusToSet) + XCTAssertEqual(actions.statusToSet!.status.status.state, BuildState.Pending) } func testMultipleIntegrationsPending() { - let (pr, bot, commit) = self.mockedPRAndBotAndCommit() + let (pr, bot, commit, statusCreator) = self.mockedPRAndBotAndCommit() let integrations = [ MockIntegration(number: 1, step: Integration.Step.Pending), MockIntegration(number: 2, step: Integration.Step.Pending), MockIntegration(number: 3, step: Integration.Step.Pending) ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", buildStatusCreator: statusCreator, integrations: integrations) //should cancel all except for the last one let toCancel = Set(actions.integrationsToCancel!) @@ -78,83 +79,83 @@ class SyncPair_PR_Bot_Tests: XCTestCase { XCTAssertTrue(toCancel.contains(integrations[0])) XCTAssertTrue(toCancel.contains(integrations[1])) XCTAssertNil(actions.startNewIntegrationBot) - XCTBAssertNotNil(actions.githubStatusToSet) - XCTAssertEqual(actions.githubStatusToSet!.status.status.state, Status.State.Pending) + XCTBAssertNotNil(actions.statusToSet) + XCTAssertEqual(actions.statusToSet!.status.status.state, BuildState.Pending) } func testOneIntegrationRunning() { - let (pr, bot, commit) = self.mockedPRAndBotAndCommit() + let (pr, bot, commit, statusCreator) = self.mockedPRAndBotAndCommit() let integrations = [ MockIntegration(number: 1, step: Integration.Step.Building), ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", buildStatusCreator: statusCreator, integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 0) XCTAssertNil(actions.startNewIntegrationBot) - XCTBAssertNotNil(actions.githubStatusToSet) - XCTAssertEqual(actions.githubStatusToSet!.status.status.state, Status.State.Pending) + XCTBAssertNotNil(actions.statusToSet) + XCTAssertEqual(actions.statusToSet!.status.status.state, BuildState.Pending) } func testOneIntegrationTestsFailed() { - let (pr, bot, commit) = self.mockedPRAndBotAndCommit() + let (pr, bot, commit, statusCreator) = self.mockedPRAndBotAndCommit() let integrations = [ MockIntegration(number: 1, step: Integration.Step.Completed, sha: "head_sha", result: Integration.Result.TestFailures) ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", buildStatusCreator: statusCreator, integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 0) XCTAssertNil(actions.startNewIntegrationBot) - XCTBAssertNotNil(actions.githubStatusToSet) - XCTAssertEqual(actions.githubStatusToSet!.status.status.state, Status.State.Failure) + XCTBAssertNotNil(actions.statusToSet) + XCTAssertEqual(actions.statusToSet!.status.status.state, BuildState.Failure) } func testOneIntegrationSuccess() { - let (pr, bot, commit) = self.mockedPRAndBotAndCommit() + let (pr, bot, commit, statusCreator) = self.mockedPRAndBotAndCommit() let integrations = [ MockIntegration(number: 1, step: Integration.Step.Completed, sha: "head_sha", result: Integration.Result.Succeeded) ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", buildStatusCreator: statusCreator, integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 0) XCTAssertNil(actions.startNewIntegrationBot) - XCTBAssertNotNil(actions.githubStatusToSet) - XCTAssertEqual(actions.githubStatusToSet!.status.status.state, Status.State.Success) + XCTBAssertNotNil(actions.statusToSet) + XCTAssertEqual(actions.statusToSet!.status.status.state, BuildState.Success) } func testTwoIntegrationOneRunningOnePending() { - let (pr, bot, commit) = self.mockedPRAndBotAndCommit() + let (pr, bot, commit, statusCreator) = self.mockedPRAndBotAndCommit() let integrations = [ MockIntegration(number: 1, step: Integration.Step.Building, sha: "head_sha"), MockIntegration(number: 2, step: Integration.Step.Pending, sha: "head_sha") ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", buildStatusCreator: statusCreator, integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 1) XCTAssertNil(actions.startNewIntegrationBot) - XCTBAssertNotNil(actions.githubStatusToSet) - XCTAssertEqual(actions.githubStatusToSet!.status.status.state, Status.State.Pending) + XCTBAssertNotNil(actions.statusToSet) + XCTAssertEqual(actions.statusToSet!.status.status.state, BuildState.Pending) } func testTwoIntegrationsDifferentCommits() { - let (pr, bot, commit) = self.mockedPRAndBotAndCommit() + let (pr, bot, commit, statusCreator) = self.mockedPRAndBotAndCommit() let integrations = [ - MockIntegration(number: 1, step: Integration.Step.Building, sha: "head_sha_old"), + MockIntegration(number: 1, step: Integration.Step.Building, sha: "old_head_sha"), ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", buildStatusCreator: statusCreator, integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 1) XCTAssertNotNil(actions.startNewIntegrationBot) - XCTBAssertNil(actions.githubStatusToSet) //no change + XCTBAssertNil(actions.statusToSet) //no change } //TODO: add more complicated cases diff --git a/BuildaKitTests/SyncerTests.swift b/BuildaKitTests/SyncerTests.swift index 84f680e..2cecc48 100644 --- a/BuildaKitTests/SyncerTests.swift +++ b/BuildaKitTests/SyncerTests.swift @@ -16,7 +16,7 @@ import BuildaKit class SyncerTests: XCTestCase { - var syncer: HDGitHubXCBotSyncer! + var syncer: StandardSyncer! override func setUp() { super.setUp() @@ -28,14 +28,14 @@ class SyncerTests: XCTestCase { super.tearDown() } - func mockedSyncer(config: SyncerConfig = SyncerConfig()) -> HDGitHubXCBotSyncer { + func mockedSyncer(config: SyncerConfig = SyncerConfig()) -> StandardSyncer { let xcodeServer = MockXcodeServer() let githubServer = MockGitHubServer() let project = MockProject() let template = MockTemplate.new() - let syncer = HDGitHubXCBotSyncer( + let syncer = StandardSyncer( integrationServer: xcodeServer, sourceServer: githubServer, project: project, @@ -60,7 +60,7 @@ class SyncerTests: XCTestCase { func testCreatingChangeActions_MultiplePR_NoBots() { - let prs = [ + let prs: [PullRequestType] = [ MockPullRequest(number: 4, title: ""), MockPullRequest(number: 7, title: "") ] @@ -100,12 +100,12 @@ class SyncerTests: XCTestCase { MockBot(name: "BuildaBot [me/Repo] |-> gh/bot_to_delete"), ] - let prs = [ + let prs: [PullRequestType] = [ MockPullRequest(number: 4, title: ""), MockPullRequest(number: 7, title: "") ] - let branches = [ + let branches: [BranchType] = [ MockBranch(name: "cd/broke_something"), MockBranch(name: "ab/fixed_errthing"), MockBranch(name: "ef/migrating_from_php_to_mongo_db") diff --git a/Buildasaur.xcodeproj/project.pbxproj b/Buildasaur.xcodeproj/project.pbxproj index 5b8898c..639545f 100644 --- a/Buildasaur.xcodeproj/project.pbxproj +++ b/Buildasaur.xcodeproj/project.pbxproj @@ -10,15 +10,22 @@ 079FD08F18890B9ED0EEC9A5 /* Pods_BuildaKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB2056DC704255FB4A676BA5 /* Pods_BuildaKitTests.framework */; }; 0E33784B1AC8F17E34DEEB74 /* Pods_Buildasaur.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E259E9106C895130138567A /* Pods_Buildasaur.framework */; }; 15A92AB3A2DE8DA7A566FB3F /* Pods_BuildaGitServerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16B84F653EF9BEA46717FE32 /* Pods_BuildaGitServerTests.framework */; }; + 3A0034F01C5975C000A4ECB5 /* bitbucket_post_status.json in Resources */ = {isa = PBXBuildFile; fileRef = 3A0034EF1C5975C000A4ECB5 /* bitbucket_post_status.json */; }; + 3A0034F21C59775100A4ECB5 /* bitbucket_get_status.json in Resources */ = {isa = PBXBuildFile; fileRef = 3A0034F11C59775100A4ECB5 /* bitbucket_get_status.json */; }; + 3A014BA51C57A94B00B82B4A /* Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A014BA41C57A94B00B82B4A /* Authentication.swift */; }; + 3A08B3611C58301D00ED7A1F /* github.png in Resources */ = {isa = PBXBuildFile; fileRef = 3A08B3601C58301D00ED7A1F /* github.png */; }; + 3A08B3641C5837EA00ED7A1F /* ServiceAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A08B3631C5837EA00ED7A1F /* ServiceAuthentication.swift */; }; + 3A0BE6E31C5A754E00AC9C57 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0BE6E21C5A754E00AC9C57 /* Extensions.swift */; }; 3A0FF5A01BBFDA7E00FB8051 /* RACUIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0FF59F1BBFDA7E00FB8051 /* RACUIExtensions.swift */; }; 3A0FF5A21BBFE8BA00FB8051 /* SyncerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0FF5A11BBFE8BA00FB8051 /* SyncerConfig.swift */; }; 3A0FF5A41BBFF9FA00FB8051 /* ProjectConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0FF5A31BBFF9FA00FB8051 /* ProjectConfig.swift */; }; 3A0FF5AE1BC0067700FB8051 /* SyncerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0FF5AD1BC0067700FB8051 /* SyncerManager.swift */; }; 3A28AF531BC984850011756E /* ConfigTriplet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A28AF521BC984850011756E /* ConfigTriplet.swift */; }; 3A3231B11B5AEF7900B53E3F /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3231B01B5AEF7900B53E3F /* Logging.swift */; }; - 3A32CD141A3D00F800861A34 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A32CD131A3D00F800861A34 /* Comment.swift */; }; - 3A32CD181A3D01E300861A34 /* Issue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A32CD171A3D01E300861A34 /* Issue.swift */; }; - 3A32CD1E1A3D2ADD00861A34 /* GitHubServerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A32CD1D1A3D2ADD00861A34 /* GitHubServerExtensions.swift */; }; + 3A32CD141A3D00F800861A34 /* GitHubComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A32CD131A3D00F800861A34 /* GitHubComment.swift */; }; + 3A32CD181A3D01E300861A34 /* GitHubIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A32CD171A3D01E300861A34 /* GitHubIssue.swift */; }; + 3A35CD6E1BE96E9B0088D57A /* BaseTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35CD6C1BE96E9B0088D57A /* BaseTypes.swift */; }; + 3A35CD6F1BE96E9B0088D57A /* SourceServerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A35CD6D1BE96E9B0088D57A /* SourceServerExtensions.swift */; }; 3A395B551BCF007000BB6947 /* LoginItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A395B541BCF007000BB6947 /* LoginItem.swift */; }; 3A3BDC1F1AF6D34900D2CD99 /* GitHubRateLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3BDC1E1AF6D34900D2CD99 /* GitHubRateLimit.swift */; }; 3A3BF8C71BD7C1680050A0B7 /* CheckoutFileParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3BF8C61BD7C1680050A0B7 /* CheckoutFileParser.swift */; }; @@ -39,7 +46,23 @@ 3A756DC51BAB425E00508B69 /* BuildaHeartbeatKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3A756DBD1BAB425E00508B69 /* BuildaHeartbeatKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3A756DCB1BAB42C200508B69 /* Heartbeat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A756DCA1BAB42C200508B69 /* Heartbeat.swift */; }; 3A756DCC1BAB44A200508B69 /* BuildaHeartbeatKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A756DBD1BAB425E00508B69 /* BuildaHeartbeatKit.framework */; }; - 3A7F0CBF1BC14E640079A511 /* BuildasaurUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7F0CBE1BC14E640079A511 /* BuildasaurUITests.swift */; }; + 3A7577AB1C593A740023F08C /* bitbucket_get_prs.json in Resources */ = {isa = PBXBuildFile; fileRef = 3A7577AA1C593A740023F08C /* bitbucket_get_prs.json */; }; + 3A7577AD1C593C5C0023F08C /* BitBucketPullRequestBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7577AC1C593C5C0023F08C /* BitBucketPullRequestBranch.swift */; }; + 3A7577AF1C59621E0023F08C /* bitbucket_get_pr.json in Resources */ = {isa = PBXBuildFile; fileRef = 3A7577AE1C59621E0023F08C /* bitbucket_get_pr.json */; }; + 3A7577B11C5963440023F08C /* bitbucket_get_repo.json in Resources */ = {isa = PBXBuildFile; fileRef = 3A7577B01C5963440023F08C /* bitbucket_get_repo.json */; }; + 3A7577B31C5963BF0023F08C /* BitBucketComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7577B21C5963BF0023F08C /* BitBucketComment.swift */; }; + 3A7577B51C5966530023F08C /* bitbucket_get_comments.json in Resources */ = {isa = PBXBuildFile; fileRef = 3A7577B41C5966530023F08C /* bitbucket_get_comments.json */; }; + 3A7577B71C59691A0023F08C /* BitBucketStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7577B61C59691A0023F08C /* BitBucketStatus.swift */; }; + 3A7AF60E1C590CC4000FD726 /* bitbucket.png in Resources */ = {isa = PBXBuildFile; fileRef = 3A7AF60D1C590CC4000FD726 /* bitbucket.png */; }; + 3A7AF6101C591FAD000FD726 /* BitBucketServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF60F1C591FAD000FD726 /* BitBucketServerTests.swift */; }; + 3A7AF6121C592023000FD726 /* BitBucketServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF6111C592023000FD726 /* BitBucketServer.swift */; }; + 3A7AF6141C592055000FD726 /* BitBucketEndpoints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF6131C592055000FD726 /* BitBucketEndpoints.swift */; }; + 3A7AF6161C5923A4000FD726 /* GitServerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF6151C5923A4000FD726 /* GitServerFactory.swift */; }; + 3A7AF6181C592919000FD726 /* BitBucketEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF6171C592919000FD726 /* BitBucketEntity.swift */; }; + 3A7AF61A1C592975000FD726 /* BitBucketPullRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF6191C592975000FD726 /* BitBucketPullRequest.swift */; }; + 3A7AF61C1C592A17000FD726 /* BitBucketIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF61B1C592A17000FD726 /* BitBucketIssue.swift */; }; + 3A7AF61E1C592A90000FD726 /* BitBucketRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF61D1C592A90000FD726 /* BitBucketRepo.swift */; }; + 3A7AF6201C592B2C000FD726 /* BitBucketRateLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7AF61F1C592B2C000FD726 /* BitBucketRateLimit.swift */; }; 3A7F0CC81BC1508C0079A511 /* GeneralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7F0CC71BC1508C0079A511 /* GeneralTests.swift */; }; 3A81BB191B5A77E9004732CD /* BuildaKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A81BB181B5A77E9004732CD /* BuildaKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3A81BB201B5A77E9004732CD /* BuildaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A81BB161B5A77E9004732CD /* BuildaKit.framework */; }; @@ -71,16 +94,15 @@ 3AAF6EFB1A3CE5BA00C657FB /* BuildaGitServer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AAF6EE41A3CE5BA00C657FB /* BuildaGitServer.framework */; }; 3AAF6EFC1A3CE5BA00C657FB /* BuildaGitServer.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AAF6EE41A3CE5BA00C657FB /* BuildaGitServer.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3AAF6F0B1A3CE82B00C657FB /* GitHubServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A58B1581A3B96C9003E0266 /* GitHubServerTests.swift */; }; - 3AB3F0E01A3CEBAC005F717F /* Branch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAF6E691A3CCA0900C657FB /* Branch.swift */; }; - 3AB3F0E11A3CEBAC005F717F /* Commit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAF6E6B1A3CCA3B00C657FB /* Commit.swift */; }; + 3AB3F0E01A3CEBAC005F717F /* GitHubBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAF6E691A3CCA0900C657FB /* GitHubBranch.swift */; }; + 3AB3F0E11A3CEBAC005F717F /* GitHubCommit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAF6E6B1A3CCA3B00C657FB /* GitHubCommit.swift */; }; 3AB3F0E21A3CEBAC005F717F /* GitHubEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAF6E671A3CC1F400C657FB /* GitHubEntity.swift */; }; - 3AB3F0E31A3CEBAC005F717F /* PullRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6355E31A3BD57D00545BF9 /* PullRequest.swift */; }; - 3AB3F0E41A3CEBAC005F717F /* PullRequestBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9DEC7A1A3BDA6C008C8270 /* PullRequestBranch.swift */; }; - 3AB3F0E51A3CEBAC005F717F /* Repo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6355E11A3BCD2800545BF9 /* Repo.swift */; }; - 3AB3F0E61A3CEBAC005F717F /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6355DF1A3BC77F00545BF9 /* User.swift */; }; - 3AB3F0E71A3CEBAC005F717F /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A38CF951A3CE94C00F41AFA /* Status.swift */; }; + 3AB3F0E31A3CEBAC005F717F /* GitHubPullRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6355E31A3BD57D00545BF9 /* GitHubPullRequest.swift */; }; + 3AB3F0E41A3CEBAC005F717F /* GitHubPullRequestBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9DEC7A1A3BDA6C008C8270 /* GitHubPullRequestBranch.swift */; }; + 3AB3F0E51A3CEBAC005F717F /* GitHubRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6355E11A3BCD2800545BF9 /* GitHubRepo.swift */; }; + 3AB3F0E61A3CEBAC005F717F /* GitHubUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6355DF1A3BC77F00545BF9 /* GitHubUser.swift */; }; + 3AB3F0E71A3CEBAC005F717F /* GitHubStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A38CF951A3CE94C00F41AFA /* GitHubStatus.swift */; }; 3AB3F0E81A3CEBAC005F717F /* GitHubEndpoints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6355D71A3BBCD200545BF9 /* GitHubEndpoints.swift */; }; - 3AB3F0E91A3CEBAC005F717F /* GitHubFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6355D91A3BBDA500545BF9 /* GitHubFactory.swift */; }; 3AB3F0EA1A3CEBAC005F717F /* GitHubServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A58B1521A3B967C003E0266 /* GitHubServer.swift */; }; 3AB3F0EB1A3CEBE9005F717F /* GitServerPublic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAF6F031A3CE66A00C657FB /* GitServerPublic.swift */; }; 3AB3FDCB1B05644300021197 /* MenuItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB3FDCA1B05644300021197 /* MenuItemManager.swift */; }; @@ -96,7 +118,7 @@ 3ACBADDF1B5ADE1400204457 /* SyncPair_PR_Bot_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBADDB1B5ADE1400204457 /* SyncPair_PR_Bot_Tests.swift */; }; 3ACBADFC1B5ADE2A00204457 /* BuildTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBADE01B5ADE2A00204457 /* BuildTemplate.swift */; }; 3ACBADFD1B5ADE2A00204457 /* CommonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBADE11B5ADE2A00204457 /* CommonExtensions.swift */; }; - 3ACBADFE1B5ADE2A00204457 /* HDGitHubXCBotSyncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBADE21B5ADE2A00204457 /* HDGitHubXCBotSyncer.swift */; }; + 3ACBADFE1B5ADE2A00204457 /* StandardSyncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBADE21B5ADE2A00204457 /* StandardSyncer.swift */; }; 3ACBADFF1B5ADE2A00204457 /* NetworkUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBADE31B5ADE2A00204457 /* NetworkUtils.swift */; }; 3ACBAE001B5ADE2A00204457 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBADE41B5ADE2A00204457 /* Persistence.swift */; }; 3ACBAE011B5ADE2A00204457 /* Project.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBADE51B5ADE2A00204457 /* Project.swift */; }; @@ -125,10 +147,13 @@ 3ACF93AA1BBEC7AB00CD888C /* XcodeScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACF93A81BBEC7AB00CD888C /* XcodeScheme.swift */; }; 3AD338B01AAE31D500ECD0F2 /* BuildTemplateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD338AF1AAE31D500ECD0F2 /* BuildTemplateViewController.swift */; }; 3AD5D2E81BD01EAF008DBB45 /* SummaryBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD5D2E71BD01EAF008DBB45 /* SummaryBuilder.swift */; }; - 3AD5D2EA1BD02144008DBB45 /* SummaryBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD5D2E91BD02144008DBB45 /* SummaryBuilderTests.swift */; }; + 3AD5D2EA1BD02144008DBB45 /* GitHubSummaryBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD5D2E91BD02144008DBB45 /* GitHubSummaryBuilderTests.swift */; }; 3AE0AB2B1BB991AB00A52D20 /* SyncerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE0AB2A1BB991AB00A52D20 /* SyncerViewModel.swift */; }; 3AE4F6CE1BBC88450006CA1C /* RACUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE4F6CD1BBC88450006CA1C /* RACUtils.swift */; }; 3AE4F6D01BBC8D250006CA1C /* EmptyProjectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE4F6CF1BBC8D250006CA1C /* EmptyProjectViewController.swift */; }; + 3AE8B79B1C52DAD20033F6EF /* Buildasaur-format-2-example2 in Resources */ = {isa = PBXBuildFile; fileRef = 3AE8B79A1C52DAD20033F6EF /* Buildasaur-format-2-example2 */; }; + 3AED13131C529BB600E3B7FF /* Buildasaur-format-3-example1 in Resources */ = {isa = PBXBuildFile; fileRef = 3AED13121C529BB600E3B7FF /* Buildasaur-format-3-example1 */; }; + 3AED13151C52A1A300E3B7FF /* SecurePersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AED13141C52A1A300E3B7FF /* SecurePersistence.swift */; }; 3AF090B81B1134AA0058567F /* BranchWatchingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF090B71B1134AA0058567F /* BranchWatchingViewController.swift */; }; 3AF1B1241AAC7CA500917EF3 /* SyncerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF1B1231AAC7CA500917EF3 /* SyncerViewController.swift */; }; 723E971AA98B199C70B7C3AA /* Pods_BuildaHeartbeatKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 406E01BAFEA2D2588BFD2D5A /* Pods_BuildaHeartbeatKit.framework */; }; @@ -144,13 +169,6 @@ remoteGlobalIDString = 3A756DBC1BAB425E00508B69; remoteInfo = BuildaHeartbeatKit; }; - 3A7F0CC11BC14E640079A511 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 3A5687681A3B93BD0066DB2B /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3A56876F1A3B93BD0066DB2B; - remoteInfo = Buildasaur; - }; 3A81BB211B5A77E9004732CD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3A5687681A3B93BD0066DB2B /* Project object */; @@ -228,16 +246,23 @@ 17FE44590CED1491A8F03F35 /* Pods-BuildaKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BuildaKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BuildaKit/Pods-BuildaKit.debug.xcconfig"; sourceTree = ""; }; 2140AD08ECF0B73145689AAD /* Pods-BuildaGitServer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BuildaGitServer.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BuildaGitServer/Pods-BuildaGitServer.debug.xcconfig"; sourceTree = ""; }; 2264BCD5DCD655137828C758 /* Pods_BuildaKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BuildaKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A0034EF1C5975C000A4ECB5 /* bitbucket_post_status.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = bitbucket_post_status.json; path = Data/bitbucket_post_status.json; sourceTree = ""; }; + 3A0034F11C59775100A4ECB5 /* bitbucket_get_status.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = bitbucket_get_status.json; path = Data/bitbucket_get_status.json; sourceTree = ""; }; + 3A014BA41C57A94B00B82B4A /* Authentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authentication.swift; sourceTree = ""; }; + 3A08B3601C58301D00ED7A1F /* github.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = github.png; path = Meta/github.png; sourceTree = SOURCE_ROOT; }; + 3A08B3631C5837EA00ED7A1F /* ServiceAuthentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceAuthentication.swift; sourceTree = ""; }; + 3A0BE6E21C5A754E00AC9C57 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 3A0FF59F1BBFDA7E00FB8051 /* RACUIExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RACUIExtensions.swift; sourceTree = ""; }; 3A0FF5A11BBFE8BA00FB8051 /* SyncerConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncerConfig.swift; sourceTree = ""; }; 3A0FF5A31BBFF9FA00FB8051 /* ProjectConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectConfig.swift; sourceTree = ""; }; 3A0FF5AD1BC0067700FB8051 /* SyncerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncerManager.swift; sourceTree = ""; }; 3A28AF521BC984850011756E /* ConfigTriplet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTriplet.swift; sourceTree = ""; }; 3A3231B01B5AEF7900B53E3F /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; - 3A32CD131A3D00F800861A34 /* Comment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = ""; }; - 3A32CD171A3D01E300861A34 /* Issue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Issue.swift; sourceTree = ""; }; - 3A32CD1D1A3D2ADD00861A34 /* GitHubServerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubServerExtensions.swift; sourceTree = ""; }; - 3A38CF951A3CE94C00F41AFA /* Status.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Status.swift; path = BuildaGitServer/Status.swift; sourceTree = SOURCE_ROOT; }; + 3A32CD131A3D00F800861A34 /* GitHubComment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubComment.swift; path = GitHub/GitHubComment.swift; sourceTree = ""; }; + 3A32CD171A3D01E300861A34 /* GitHubIssue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubIssue.swift; path = GitHub/GitHubIssue.swift; sourceTree = ""; }; + 3A35CD6C1BE96E9B0088D57A /* BaseTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTypes.swift; sourceTree = ""; }; + 3A35CD6D1BE96E9B0088D57A /* SourceServerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceServerExtensions.swift; sourceTree = ""; }; + 3A38CF951A3CE94C00F41AFA /* GitHubStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubStatus.swift; path = GitHub/GitHubStatus.swift; sourceTree = ""; }; 3A395B541BCF007000BB6947 /* LoginItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginItem.swift; sourceTree = ""; }; 3A3BDC1E1AF6D34900D2CD99 /* GitHubRateLimit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubRateLimit.swift; sourceTree = ""; }; 3A3BF8C61BD7C1680050A0B7 /* CheckoutFileParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutFileParser.swift; sourceTree = ""; }; @@ -254,22 +279,35 @@ 3A5687751A3B93BD0066DB2B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 3A5687791A3B93BD0066DB2B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 3A56877C1A3B93BD0066DB2B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 3A58B1521A3B967C003E0266 /* GitHubServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubServer.swift; path = BuildaGitServer/GitHubServer.swift; sourceTree = SOURCE_ROOT; }; + 3A58B1521A3B967C003E0266 /* GitHubServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubServer.swift; sourceTree = ""; }; 3A58B1581A3B96C9003E0266 /* GitHubServerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubServerTests.swift; sourceTree = ""; }; 3A5B04A91AB4ABEB00F60536 /* TriggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TriggerViewController.swift; sourceTree = ""; }; 3A5B04AF1AB5144700F60536 /* ManualBotManagementViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManualBotManagementViewController.swift; sourceTree = ""; }; - 3A6355D71A3BBCD200545BF9 /* GitHubEndpoints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubEndpoints.swift; path = BuildaGitServer/GitHubEndpoints.swift; sourceTree = SOURCE_ROOT; }; - 3A6355D91A3BBDA500545BF9 /* GitHubFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubFactory.swift; path = BuildaGitServer/GitHubFactory.swift; sourceTree = SOURCE_ROOT; }; - 3A6355DF1A3BC77F00545BF9 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = User.swift; path = BuildaGitServer/User.swift; sourceTree = SOURCE_ROOT; }; - 3A6355E11A3BCD2800545BF9 /* Repo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Repo.swift; path = BuildaGitServer/Repo.swift; sourceTree = SOURCE_ROOT; }; - 3A6355E31A3BD57D00545BF9 /* PullRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PullRequest.swift; path = BuildaGitServer/PullRequest.swift; sourceTree = SOURCE_ROOT; }; + 3A6355D71A3BBCD200545BF9 /* GitHubEndpoints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubEndpoints.swift; sourceTree = ""; }; + 3A6355DF1A3BC77F00545BF9 /* GitHubUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubUser.swift; path = GitHub/GitHubUser.swift; sourceTree = ""; }; + 3A6355E11A3BCD2800545BF9 /* GitHubRepo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubRepo.swift; path = GitHub/GitHubRepo.swift; sourceTree = ""; }; + 3A6355E31A3BD57D00545BF9 /* GitHubPullRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubPullRequest.swift; path = GitHub/GitHubPullRequest.swift; sourceTree = ""; }; 3A756DBD1BAB425E00508B69 /* BuildaHeartbeatKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BuildaHeartbeatKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3A756DBF1BAB425E00508B69 /* BuildaHeartbeatKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuildaHeartbeatKit.h; sourceTree = ""; }; 3A756DC11BAB425E00508B69 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3A756DCA1BAB42C200508B69 /* Heartbeat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Heartbeat.swift; sourceTree = ""; }; - 3A7F0CBC1BC14E640079A511 /* BuildasaurUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BuildasaurUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7F0CBE1BC14E640079A511 /* BuildasaurUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildasaurUITests.swift; sourceTree = ""; }; - 3A7F0CC01BC14E640079A511 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3A7577AA1C593A740023F08C /* bitbucket_get_prs.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = bitbucket_get_prs.json; path = Data/bitbucket_get_prs.json; sourceTree = ""; }; + 3A7577AC1C593C5C0023F08C /* BitBucketPullRequestBranch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketPullRequestBranch.swift; path = BitBucket/BitBucketPullRequestBranch.swift; sourceTree = ""; }; + 3A7577AE1C59621E0023F08C /* bitbucket_get_pr.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = bitbucket_get_pr.json; path = Data/bitbucket_get_pr.json; sourceTree = ""; }; + 3A7577B01C5963440023F08C /* bitbucket_get_repo.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = bitbucket_get_repo.json; path = Data/bitbucket_get_repo.json; sourceTree = ""; }; + 3A7577B21C5963BF0023F08C /* BitBucketComment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketComment.swift; path = BitBucket/BitBucketComment.swift; sourceTree = ""; }; + 3A7577B41C5966530023F08C /* bitbucket_get_comments.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = bitbucket_get_comments.json; path = Data/bitbucket_get_comments.json; sourceTree = ""; }; + 3A7577B61C59691A0023F08C /* BitBucketStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketStatus.swift; path = BitBucket/BitBucketStatus.swift; sourceTree = ""; }; + 3A7AF60D1C590CC4000FD726 /* bitbucket.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = bitbucket.png; path = Meta/bitbucket.png; sourceTree = SOURCE_ROOT; }; + 3A7AF60F1C591FAD000FD726 /* BitBucketServerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitBucketServerTests.swift; sourceTree = ""; }; + 3A7AF6111C592023000FD726 /* BitBucketServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketServer.swift; path = BitBucket/BitBucketServer.swift; sourceTree = ""; }; + 3A7AF6131C592055000FD726 /* BitBucketEndpoints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketEndpoints.swift; path = BitBucket/BitBucketEndpoints.swift; sourceTree = ""; }; + 3A7AF6151C5923A4000FD726 /* GitServerFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitServerFactory.swift; sourceTree = ""; }; + 3A7AF6171C592919000FD726 /* BitBucketEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketEntity.swift; path = BitBucket/BitBucketEntity.swift; sourceTree = ""; }; + 3A7AF6191C592975000FD726 /* BitBucketPullRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketPullRequest.swift; path = BitBucket/BitBucketPullRequest.swift; sourceTree = ""; }; + 3A7AF61B1C592A17000FD726 /* BitBucketIssue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketIssue.swift; path = BitBucket/BitBucketIssue.swift; sourceTree = ""; }; + 3A7AF61D1C592A90000FD726 /* BitBucketRepo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketRepo.swift; path = BitBucket/BitBucketRepo.swift; sourceTree = ""; }; + 3A7AF61F1C592B2C000FD726 /* BitBucketRateLimit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitBucketRateLimit.swift; path = BitBucket/BitBucketRateLimit.swift; sourceTree = ""; }; 3A7F0CC71BC1508C0079A511 /* GeneralTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralTests.swift; sourceTree = ""; }; 3A81BB161B5A77E9004732CD /* BuildaKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BuildaKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3A81BB181B5A77E9004732CD /* BuildaKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuildaKit.h; sourceTree = ""; }; @@ -291,15 +329,15 @@ 3A9D74211BCC041600DCA23C /* Buildasaur-format-0-example1 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Buildasaur-format-0-example1"; path = "Migration/Buildasaur-format-0-example1"; sourceTree = ""; }; 3A9D74231BCC103E00DCA23C /* Buildasaur-format-1-example1 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Buildasaur-format-1-example1"; path = "Migration/Buildasaur-format-1-example1"; sourceTree = ""; }; 3A9D74251BCC19BB00DCA23C /* Buildasaur-format-2-example1 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Buildasaur-format-2-example1"; path = "Migration/Buildasaur-format-2-example1"; sourceTree = ""; }; - 3A9DEC7A1A3BDA6C008C8270 /* PullRequestBranch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PullRequestBranch.swift; path = BuildaGitServer/PullRequestBranch.swift; sourceTree = SOURCE_ROOT; }; + 3A9DEC7A1A3BDA6C008C8270 /* GitHubPullRequestBranch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubPullRequestBranch.swift; path = GitHub/GitHubPullRequestBranch.swift; sourceTree = ""; }; 3AA573051BCD70FC00172D3E /* URLUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLUtils.swift; sourceTree = ""; }; 3AAA1B651AAB6E9800FA1598 /* SeparatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeparatorView.swift; sourceTree = ""; }; 3AAA1B671AAB722600FA1598 /* ProjectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectViewController.swift; sourceTree = ""; }; 3AAA1B751AAC504700FA1598 /* XcodeServerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeServerViewController.swift; sourceTree = ""; }; 3AAA1B771AAC508A00FA1598 /* ConfigEditViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigEditViewController.swift; sourceTree = ""; }; - 3AAF6E671A3CC1F400C657FB /* GitHubEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubEntity.swift; path = BuildaGitServer/GitHubEntity.swift; sourceTree = SOURCE_ROOT; }; - 3AAF6E691A3CCA0900C657FB /* Branch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Branch.swift; path = BuildaGitServer/Branch.swift; sourceTree = SOURCE_ROOT; }; - 3AAF6E6B1A3CCA3B00C657FB /* Commit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Commit.swift; path = BuildaGitServer/Commit.swift; sourceTree = SOURCE_ROOT; }; + 3AAF6E671A3CC1F400C657FB /* GitHubEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubEntity.swift; path = GitHub/GitHubEntity.swift; sourceTree = ""; }; + 3AAF6E691A3CCA0900C657FB /* GitHubBranch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubBranch.swift; path = GitHub/GitHubBranch.swift; sourceTree = ""; }; + 3AAF6E6B1A3CCA3B00C657FB /* GitHubCommit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubCommit.swift; path = GitHub/GitHubCommit.swift; sourceTree = ""; }; 3AAF6EE41A3CE5BA00C657FB /* BuildaGitServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BuildaGitServer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3AAF6EE71A3CE5BA00C657FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3AAF6EE81A3CE5BA00C657FB /* BuildaGitServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuildaGitServer.h; sourceTree = ""; }; @@ -319,7 +357,7 @@ 3ACBADDB1B5ADE1400204457 /* SyncPair_PR_Bot_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncPair_PR_Bot_Tests.swift; sourceTree = ""; }; 3ACBADE01B5ADE2A00204457 /* BuildTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BuildTemplate.swift; sourceTree = ""; }; 3ACBADE11B5ADE2A00204457 /* CommonExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonExtensions.swift; sourceTree = ""; }; - 3ACBADE21B5ADE2A00204457 /* HDGitHubXCBotSyncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HDGitHubXCBotSyncer.swift; sourceTree = ""; }; + 3ACBADE21B5ADE2A00204457 /* StandardSyncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardSyncer.swift; sourceTree = ""; }; 3ACBADE31B5ADE2A00204457 /* NetworkUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkUtils.swift; sourceTree = ""; }; 3ACBADE41B5ADE2A00204457 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; 3ACBADE51B5ADE2A00204457 /* Project.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Project.swift; sourceTree = ""; }; @@ -348,10 +386,13 @@ 3ACF93A81BBEC7AB00CD888C /* XcodeScheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeScheme.swift; sourceTree = ""; }; 3AD338AF1AAE31D500ECD0F2 /* BuildTemplateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BuildTemplateViewController.swift; sourceTree = ""; }; 3AD5D2E71BD01EAF008DBB45 /* SummaryBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SummaryBuilder.swift; sourceTree = ""; }; - 3AD5D2E91BD02144008DBB45 /* SummaryBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SummaryBuilderTests.swift; sourceTree = ""; }; + 3AD5D2E91BD02144008DBB45 /* GitHubSummaryBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubSummaryBuilderTests.swift; sourceTree = ""; }; 3AE0AB2A1BB991AB00A52D20 /* SyncerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncerViewModel.swift; sourceTree = ""; }; 3AE4F6CD1BBC88450006CA1C /* RACUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RACUtils.swift; sourceTree = ""; }; 3AE4F6CF1BBC8D250006CA1C /* EmptyProjectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyProjectViewController.swift; sourceTree = ""; }; + 3AE8B79A1C52DAD20033F6EF /* Buildasaur-format-2-example2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Buildasaur-format-2-example2"; path = "Migration/Buildasaur-format-2-example2"; sourceTree = ""; }; + 3AED13121C529BB600E3B7FF /* Buildasaur-format-3-example1 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Buildasaur-format-3-example1"; path = "Migration/Buildasaur-format-3-example1"; sourceTree = ""; }; + 3AED13141C52A1A300E3B7FF /* SecurePersistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurePersistence.swift; sourceTree = ""; }; 3AF090B71B1134AA0058567F /* BranchWatchingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BranchWatchingViewController.swift; sourceTree = ""; }; 3AF1B1231AAC7CA500917EF3 /* SyncerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncerViewController.swift; sourceTree = ""; }; 406E01BAFEA2D2588BFD2D5A /* Pods_BuildaHeartbeatKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BuildaHeartbeatKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -391,13 +432,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 3A7F0CB91BC14E640079A511 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 3A81BB121B5A77E9004732CD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -462,6 +496,31 @@ name = Pods; sourceTree = ""; }; + 3A08B35D1C582FF700ED7A1F /* Service Logos */ = { + isa = PBXGroup; + children = ( + 3A7AF60D1C590CC4000FD726 /* bitbucket.png */, + 3A08B3601C58301D00ED7A1F /* github.png */, + ); + name = "Service Logos"; + sourceTree = ""; + }; + 3A08B3621C5837D400ED7A1F /* Authentication */ = { + isa = PBXGroup; + children = ( + 3A08B3631C5837EA00ED7A1F /* ServiceAuthentication.swift */, + ); + name = Authentication; + sourceTree = ""; + }; + 3A0BE6E11C5A753F00AC9C57 /* Utils */ = { + isa = PBXGroup; + children = ( + 3A0BE6E21C5A754E00AC9C57 /* Extensions.swift */, + ); + name = Utils; + sourceTree = ""; + }; 3A0FF59E1BBFDA5C00FB8051 /* Utils */ = { isa = PBXGroup; children = ( @@ -472,11 +531,21 @@ name = Utils; sourceTree = ""; }; + 3A35CD6B1BE96E880088D57A /* Base */ = { + isa = PBXGroup; + children = ( + 3A014BA41C57A94B00B82B4A /* Authentication.swift */, + 3A35CD6C1BE96E9B0088D57A /* BaseTypes.swift */, + 3A7AF6151C5923A4000FD726 /* GitServerFactory.swift */, + 3A35CD6D1BE96E9B0088D57A /* SourceServerExtensions.swift */, + ); + path = Base; + sourceTree = ""; + }; 3A5687671A3B93BD0066DB2B = { isa = PBXGroup; children = ( 3A5687721A3B93BD0066DB2B /* Buildasaur */, - 3A7F0CBD1BC14E640079A511 /* BuildasaurUITests */, 3A81BB171B5A77E9004732CD /* BuildaKit */, 3A81BB251B5A77E9004732CD /* BuildaKitTests */, 3AAF6EE51A3CE5BA00C657FB /* BuildaGitServer */, @@ -497,7 +566,6 @@ 3A81BB161B5A77E9004732CD /* BuildaKit.framework */, 3A81BB1F1B5A77E9004732CD /* BuildaKitTests.xctest */, 3A756DBD1BAB425E00508B69 /* BuildaHeartbeatKit.framework */, - 3A7F0CBC1BC14E640079A511 /* BuildasaurUITests.xctest */, ); name = Products; sourceTree = ""; @@ -513,6 +581,7 @@ 3AAA1B631AAB6E6800FA1598 /* ViewController */, 3AE0AB291BB9919700A52D20 /* ViewModels */, 3AAA1B641AAB6E8500FA1598 /* Views */, + 3A08B3621C5837D400ED7A1F /* Authentication */, 3A0FF59E1BBFDA5C00FB8051 /* Utils */, ); path = Buildasaur; @@ -521,6 +590,7 @@ 3A5687731A3B93BD0066DB2B /* Supporting Files */ = { isa = PBXGroup; children = ( + 3A08B35D1C582FF700ED7A1F /* Service Logos */, 3A5687741A3B93BD0066DB2B /* Info.plist */, 3A9BB91C1BCEFE8F0049C0C0 /* launch_item.plist */, ); @@ -532,28 +602,26 @@ children = ( 3A6355DE1A3BC77500545BF9 /* Entities */, 3A6355D71A3BBCD200545BF9 /* GitHubEndpoints.swift */, - 3A6355D91A3BBDA500545BF9 /* GitHubFactory.swift */, 3A58B1521A3B967C003E0266 /* GitHubServer.swift */, - 3A32CD1D1A3D2ADD00861A34 /* GitHubServerExtensions.swift */, 3A3BDC1E1AF6D34900D2CD99 /* GitHubRateLimit.swift */, ); name = GitHub; - path = BuildaGitServer; + path = BuildaGitServer/GitHub; sourceTree = SOURCE_ROOT; }; 3A6355DE1A3BC77500545BF9 /* Entities */ = { isa = PBXGroup; children = ( - 3AAF6E691A3CCA0900C657FB /* Branch.swift */, - 3A32CD131A3D00F800861A34 /* Comment.swift */, - 3AAF6E6B1A3CCA3B00C657FB /* Commit.swift */, + 3AAF6E691A3CCA0900C657FB /* GitHubBranch.swift */, + 3A32CD131A3D00F800861A34 /* GitHubComment.swift */, + 3AAF6E6B1A3CCA3B00C657FB /* GitHubCommit.swift */, 3AAF6E671A3CC1F400C657FB /* GitHubEntity.swift */, - 3A32CD171A3D01E300861A34 /* Issue.swift */, - 3A6355E31A3BD57D00545BF9 /* PullRequest.swift */, - 3A9DEC7A1A3BDA6C008C8270 /* PullRequestBranch.swift */, - 3A6355E11A3BCD2800545BF9 /* Repo.swift */, - 3A38CF951A3CE94C00F41AFA /* Status.swift */, - 3A6355DF1A3BC77F00545BF9 /* User.swift */, + 3A32CD171A3D01E300861A34 /* GitHubIssue.swift */, + 3A6355E31A3BD57D00545BF9 /* GitHubPullRequest.swift */, + 3A9DEC7A1A3BDA6C008C8270 /* GitHubPullRequestBranch.swift */, + 3A6355E11A3BCD2800545BF9 /* GitHubRepo.swift */, + 3A38CF951A3CE94C00F41AFA /* GitHubStatus.swift */, + 3A6355DF1A3BC77F00545BF9 /* GitHubUser.swift */, ); name = Entities; path = BuildaGitServer; @@ -569,13 +637,17 @@ path = BuildaHeartbeatKit; sourceTree = ""; }; - 3A7F0CBD1BC14E640079A511 /* BuildasaurUITests */ = { + 3A7577A91C593A6A0023F08C /* Data */ = { isa = PBXGroup; children = ( - 3A7F0CBE1BC14E640079A511 /* BuildasaurUITests.swift */, - 3A7F0CC01BC14E640079A511 /* Info.plist */, - ); - path = BuildasaurUITests; + 3A0034F11C59775100A4ECB5 /* bitbucket_get_status.json */, + 3A0034EF1C5975C000A4ECB5 /* bitbucket_post_status.json */, + 3A7577B41C5966530023F08C /* bitbucket_get_comments.json */, + 3A7577B01C5963440023F08C /* bitbucket_get_repo.json */, + 3A7577AE1C59621E0023F08C /* bitbucket_get_pr.json */, + 3A7577AA1C593A740023F08C /* bitbucket_get_prs.json */, + ); + name = Data; sourceTree = ""; }; 3A81BB171B5A77E9004732CD /* BuildaKit */ = { @@ -599,7 +671,7 @@ 3A7F0CC71BC1508C0079A511 /* GeneralTests.swift */, 3ACBADD61B5ADE0E00204457 /* MockHelpers.swift */, 3ACBADD81B5ADE1400204457 /* Mocks.swift */, - 3AD5D2E91BD02144008DBB45 /* SummaryBuilderTests.swift */, + 3AD5D2E91BD02144008DBB45 /* GitHubSummaryBuilderTests.swift */, 3ACBADDA1B5ADE1400204457 /* SyncerTests.swift */, 3ACBADDB1B5ADE1400204457 /* SyncPair_PR_Bot_Tests.swift */, 3A9D741E1BCBF86900DCA23C /* Migration */, @@ -624,9 +696,11 @@ 3A9D741E1BCBF86900DCA23C /* Migration */ = { isa = PBXGroup; children = ( - 3A9D74251BCC19BB00DCA23C /* Buildasaur-format-2-example1 */, - 3A9D74231BCC103E00DCA23C /* Buildasaur-format-1-example1 */, 3A9D74211BCC041600DCA23C /* Buildasaur-format-0-example1 */, + 3A9D74231BCC103E00DCA23C /* Buildasaur-format-1-example1 */, + 3A9D74251BCC19BB00DCA23C /* Buildasaur-format-2-example1 */, + 3AE8B79A1C52DAD20033F6EF /* Buildasaur-format-2-example2 */, + 3AED13121C529BB600E3B7FF /* Buildasaur-format-3-example1 */, 3A9D741F1BCBF87900DCA23C /* MigrationTests.swift */, ); name = Migration; @@ -655,10 +729,13 @@ 3AAF6EE51A3CE5BA00C657FB /* BuildaGitServer */ = { isa = PBXGroup; children = ( - 3AAF6F031A3CE66A00C657FB /* GitServerPublic.swift */, 3AAF6EE81A3CE5BA00C657FB /* BuildaGitServer.h */, + 3AAF6F031A3CE66A00C657FB /* GitServerPublic.swift */, + 3A35CD6B1BE96E880088D57A /* Base */, + 3ABF0ADC1C51A47A00882A40 /* BitBucket */, 3A6355DB1A3BBDB000545BF9 /* GitHub */, 3AAF6EE61A3CE5BA00C657FB /* Supporting Files */, + 3A0BE6E11C5A753F00AC9C57 /* Utils */, ); path = BuildaGitServer; sourceTree = ""; @@ -674,6 +751,8 @@ 3AAF6EF41A3CE5BA00C657FB /* BuildaGitServerTests */ = { isa = PBXGroup; children = ( + 3A7577A91C593A6A0023F08C /* Data */, + 3A7AF60F1C591FAD000FD726 /* BitBucketServerTests.swift */, 3A58B1581A3B96C9003E0266 /* GitHubServerTests.swift */, 3AAF6EF51A3CE5BA00C657FB /* Supporting Files */, ); @@ -688,6 +767,31 @@ name = "Supporting Files"; sourceTree = ""; }; + 3ABF0ADC1C51A47A00882A40 /* BitBucket */ = { + isa = PBXGroup; + children = ( + 3ABF0ADD1C51A48C00882A40 /* Entities */, + 3A7AF6111C592023000FD726 /* BitBucketServer.swift */, + 3A7AF6131C592055000FD726 /* BitBucketEndpoints.swift */, + 3A7AF61F1C592B2C000FD726 /* BitBucketRateLimit.swift */, + ); + name = BitBucket; + sourceTree = ""; + }; + 3ABF0ADD1C51A48C00882A40 /* Entities */ = { + isa = PBXGroup; + children = ( + 3A7AF6171C592919000FD726 /* BitBucketEntity.swift */, + 3A7AF6191C592975000FD726 /* BitBucketPullRequest.swift */, + 3A7577AC1C593C5C0023F08C /* BitBucketPullRequestBranch.swift */, + 3A7AF61B1C592A17000FD726 /* BitBucketIssue.swift */, + 3A7AF61D1C592A90000FD726 /* BitBucketRepo.swift */, + 3A7577B21C5963BF0023F08C /* BitBucketComment.swift */, + 3A7577B61C59691A0023F08C /* BitBucketStatus.swift */, + ); + name = Entities; + sourceTree = ""; + }; 3AC156251BB95C130068A1A3 /* Dashboard */ = { isa = PBXGroup; children = ( @@ -721,6 +825,7 @@ 3ACBADE71B5ADE2A00204457 /* StorageManager.swift */, 3A9D741C1BCBDDA200DCA23C /* PersistenceMigrator.swift */, 3A395B541BCF007000BB6947 /* LoginItem.swift */, + 3AED13141C52A1A300E3B7FF /* SecurePersistence.swift */, ); name = Persistence; sourceTree = ""; @@ -751,7 +856,7 @@ 3AC1562A1BB964A00068A1A3 /* Syncing Logic */ = { isa = PBXGroup; children = ( - 3ACBADE21B5ADE2A00204457 /* HDGitHubXCBotSyncer.swift */, + 3ACBADE21B5ADE2A00204457 /* StandardSyncer.swift */, 3AD5D2E71BD01EAF008DBB45 /* SummaryBuilder.swift */, 3ACBADE91B5ADE2A00204457 /* Syncer.swift */, 3ACBADEA1B5ADE2A00204457 /* SyncerBotManipulation.swift */, @@ -910,24 +1015,6 @@ productReference = 3A756DBD1BAB425E00508B69 /* BuildaHeartbeatKit.framework */; productType = "com.apple.product-type.framework"; }; - 3A7F0CBB1BC14E640079A511 /* BuildasaurUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3A7F0CC61BC14E640079A511 /* Build configuration list for PBXNativeTarget "BuildasaurUITests" */; - buildPhases = ( - 3A7F0CB81BC14E640079A511 /* Sources */, - 3A7F0CB91BC14E640079A511 /* Frameworks */, - 3A7F0CBA1BC14E640079A511 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 3A7F0CC21BC14E640079A511 /* PBXTargetDependency */, - ); - name = BuildasaurUITests; - productName = BuildasaurUITests; - productReference = 3A7F0CBC1BC14E640079A511 /* BuildasaurUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; 3A81BB151B5A77E9004732CD /* BuildaKit */ = { isa = PBXNativeTarget; buildConfigurationList = 3A81BB331B5A77E9004732CD /* Build configuration list for PBXNativeTarget "BuildaKit" */; @@ -1036,10 +1123,6 @@ CreatedOnToolsVersion = 7.0; DevelopmentTeam = 7BJ2984YDK; }; - 3A7F0CBB1BC14E640079A511 = { - CreatedOnToolsVersion = 7.0.1; - TestTargetID = 3A56876F1A3B93BD0066DB2B; - }; 3A81BB151B5A77E9004732CD = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = 7BJ2984YDK; @@ -1071,7 +1154,6 @@ projectRoot = ""; targets = ( 3A56876F1A3B93BD0066DB2B /* Buildasaur */, - 3A7F0CBB1BC14E640079A511 /* BuildasaurUITests */, 3A81BB151B5A77E9004732CD /* BuildaKit */, 3A81BB1E1B5A77E9004732CD /* BuildaKitTests */, 3AAF6EE31A3CE5BA00C657FB /* BuildaGitServer */, @@ -1086,6 +1168,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3A7AF60E1C590CC4000FD726 /* bitbucket.png in Resources */, + 3A08B3611C58301D00ED7A1F /* github.png in Resources */, 3A56877A1A3B93BD0066DB2B /* Images.xcassets in Resources */, 3A56877D1A3B93BD0066DB2B /* Main.storyboard in Resources */, 3A9BB91D1BCEFE8F0049C0C0 /* launch_item.plist in Resources */, @@ -1099,13 +1183,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 3A7F0CBA1BC14E640079A511 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 3A81BB141B5A77E9004732CD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1118,8 +1195,10 @@ buildActionMask = 2147483647; files = ( 3A9D74221BCC041600DCA23C /* Buildasaur-format-0-example1 in Resources */, + 3AED13131C529BB600E3B7FF /* Buildasaur-format-3-example1 in Resources */, 3ACBADDD1B5ADE1400204457 /* sampleFinishedIntegration.json in Resources */, 3A9D74241BCC103E00DCA23C /* Buildasaur-format-1-example1 in Resources */, + 3AE8B79B1C52DAD20033F6EF /* Buildasaur-format-2-example2 in Resources */, 3A9D74261BCC19BB00DCA23C /* Buildasaur-format-2-example1 in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1135,6 +1214,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3A7577B51C5966530023F08C /* bitbucket_get_comments.json in Resources */, + 3A7577AF1C59621E0023F08C /* bitbucket_get_pr.json in Resources */, + 3A0034F21C59775100A4ECB5 /* bitbucket_get_status.json in Resources */, + 3A0034F01C5975C000A4ECB5 /* bitbucket_post_status.json in Resources */, + 3A7577B11C5963440023F08C /* bitbucket_get_repo.json in Resources */, + 3A7577AB1C593A740023F08C /* bitbucket_get_prs.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1414,6 +1499,7 @@ 3A9109C31BC2B05C00C2AECA /* MainEditor_EditeeDelegate.swift in Sources */, 3A9109BD1BC27C5500C2AECA /* EditorState.swift in Sources */, 3AAA1B681AAB722600FA1598 /* ProjectViewController.swift in Sources */, + 3A08B3641C5837EA00ED7A1F /* ServiceAuthentication.swift in Sources */, 3AB3FDCB1B05644300021197 /* MenuItemManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1426,14 +1512,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 3A7F0CB81BC14E640079A511 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7F0CBF1BC14E640079A511 /* BuildasaurUITests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 3A81BB111B5A77E9004732CD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1443,13 +1521,14 @@ 3A0FF5A41BBFF9FA00FB8051 /* ProjectConfig.swift in Sources */, 3ACBAE0E1B5ADE2A00204457 /* SyncPair_PR_NoBot.swift in Sources */, 3ACF93AA1BBEC7AB00CD888C /* XcodeScheme.swift in Sources */, + 3AED13151C52A1A300E3B7FF /* SecurePersistence.swift in Sources */, 3A28AF531BC984850011756E /* ConfigTriplet.swift in Sources */, 3ACBADFD1B5ADE2A00204457 /* CommonExtensions.swift in Sources */, 3ACBAE131B5ADE2A00204457 /* SyncPairResolver.swift in Sources */, 3A3BF8C91BD7C1920050A0B7 /* BlueprintFileParser.swift in Sources */, 3A4C15A01BBB2DD0007FA970 /* WorkspaceMetadata.swift in Sources */, 3ACBAE0A1B5ADE2A00204457 /* SyncPair_Branch_Bot.swift in Sources */, - 3ACBADFE1B5ADE2A00204457 /* HDGitHubXCBotSyncer.swift in Sources */, + 3ACBADFE1B5ADE2A00204457 /* StandardSyncer.swift in Sources */, 3ACBAE071B5ADE2A00204457 /* SyncerBotNaming.swift in Sources */, 3ACBAE0C1B5ADE2A00204457 /* SyncPair_Deletable_Bot.swift in Sources */, 3ACBAE171B5ADE2A00204457 /* XcodeServerSyncerUtils.swift in Sources */, @@ -1493,7 +1572,7 @@ buildActionMask = 2147483647; files = ( 3ACBADD71B5ADE0E00204457 /* MockHelpers.swift in Sources */, - 3AD5D2EA1BD02144008DBB45 /* SummaryBuilderTests.swift in Sources */, + 3AD5D2EA1BD02144008DBB45 /* GitHubSummaryBuilderTests.swift in Sources */, 3ACBADDE1B5ADE1400204457 /* SyncerTests.swift in Sources */, 3ACBADDF1B5ADE1400204457 /* SyncPair_PR_Bot_Tests.swift in Sources */, 3ACBADDC1B5ADE1400204457 /* Mocks.swift in Sources */, @@ -1506,22 +1585,35 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3A7AF6141C592055000FD726 /* BitBucketEndpoints.swift in Sources */, 3AB3F0E21A3CEBAC005F717F /* GitHubEntity.swift in Sources */, - 3A32CD181A3D01E300861A34 /* Issue.swift in Sources */, - 3AB3F0E11A3CEBAC005F717F /* Commit.swift in Sources */, - 3AB3F0E31A3CEBAC005F717F /* PullRequest.swift in Sources */, - 3AB3F0E71A3CEBAC005F717F /* Status.swift in Sources */, - 3AB3F0E51A3CEBAC005F717F /* Repo.swift in Sources */, - 3AB3F0E01A3CEBAC005F717F /* Branch.swift in Sources */, + 3A32CD181A3D01E300861A34 /* GitHubIssue.swift in Sources */, + 3AB3F0E11A3CEBAC005F717F /* GitHubCommit.swift in Sources */, + 3A7577AD1C593C5C0023F08C /* BitBucketPullRequestBranch.swift in Sources */, + 3A7577B71C59691A0023F08C /* BitBucketStatus.swift in Sources */, + 3A7AF6161C5923A4000FD726 /* GitServerFactory.swift in Sources */, + 3A0BE6E31C5A754E00AC9C57 /* Extensions.swift in Sources */, + 3AB3F0E31A3CEBAC005F717F /* GitHubPullRequest.swift in Sources */, + 3AB3F0E71A3CEBAC005F717F /* GitHubStatus.swift in Sources */, + 3AB3F0E51A3CEBAC005F717F /* GitHubRepo.swift in Sources */, + 3A7AF6181C592919000FD726 /* BitBucketEntity.swift in Sources */, + 3A7AF61E1C592A90000FD726 /* BitBucketRepo.swift in Sources */, + 3AB3F0E01A3CEBAC005F717F /* GitHubBranch.swift in Sources */, 3AB3F0EB1A3CEBE9005F717F /* GitServerPublic.swift in Sources */, 3AB3F0E81A3CEBAC005F717F /* GitHubEndpoints.swift in Sources */, - 3A32CD141A3D00F800861A34 /* Comment.swift in Sources */, + 3A7AF61C1C592A17000FD726 /* BitBucketIssue.swift in Sources */, + 3A32CD141A3D00F800861A34 /* GitHubComment.swift in Sources */, + 3A35CD6E1BE96E9B0088D57A /* BaseTypes.swift in Sources */, + 3A7577B31C5963BF0023F08C /* BitBucketComment.swift in Sources */, 3AB3F0EA1A3CEBAC005F717F /* GitHubServer.swift in Sources */, - 3AB3F0E41A3CEBAC005F717F /* PullRequestBranch.swift in Sources */, - 3AB3F0E61A3CEBAC005F717F /* User.swift in Sources */, - 3AB3F0E91A3CEBAC005F717F /* GitHubFactory.swift in Sources */, + 3AB3F0E41A3CEBAC005F717F /* GitHubPullRequestBranch.swift in Sources */, + 3AB3F0E61A3CEBAC005F717F /* GitHubUser.swift in Sources */, + 3A7AF6121C592023000FD726 /* BitBucketServer.swift in Sources */, + 3A35CD6F1BE96E9B0088D57A /* SourceServerExtensions.swift in Sources */, + 3A7AF6201C592B2C000FD726 /* BitBucketRateLimit.swift in Sources */, 3A3BDC1F1AF6D34900D2CD99 /* GitHubRateLimit.swift in Sources */, - 3A32CD1E1A3D2ADD00861A34 /* GitHubServerExtensions.swift in Sources */, + 3A7AF61A1C592975000FD726 /* BitBucketPullRequest.swift in Sources */, + 3A014BA51C57A94B00B82B4A /* Authentication.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1529,6 +1621,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3A7AF6101C591FAD000FD726 /* BitBucketServerTests.swift in Sources */, 3AAF6F0B1A3CE82B00C657FB /* GitHubServerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1541,11 +1634,6 @@ target = 3A756DBC1BAB425E00508B69 /* BuildaHeartbeatKit */; targetProxy = 3A756DC21BAB425E00508B69 /* PBXContainerItemProxy */; }; - 3A7F0CC21BC14E640079A511 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3A56876F1A3B93BD0066DB2B /* Buildasaur */; - targetProxy = 3A7F0CC11BC14E640079A511 /* PBXContainerItemProxy */; - }; 3A81BB221B5A77E9004732CD /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3A81BB151B5A77E9004732CD /* BuildaKit */; @@ -1904,56 +1992,6 @@ }; name = Testing; }; - 3A7F0CC31BC14E640079A511 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = BuildasaurUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = com.honzadvorsky.BuildasaurUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_TARGET_NAME = Buildasaur; - USES_XCTRUNNER = YES; - }; - name = Debug; - }; - 3A7F0CC41BC14E640079A511 /* Testing */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - ENABLE_NS_ASSERTIONS = YES; - ENABLE_TESTABILITY = YES; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = BuildasaurUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = com.honzadvorsky.BuildasaurUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_TARGET_NAME = Buildasaur; - USES_XCTRUNNER = YES; - }; - name = Testing; - }; - 3A7F0CC51BC14E640079A511 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - ENABLE_NS_ASSERTIONS = YES; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = BuildasaurUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = com.honzadvorsky.BuildasaurUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_TARGET_NAME = Buildasaur; - USES_XCTRUNNER = YES; - }; - name = Release; - }; 3A81BB2D1B5A77E9004732CD /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 17FE44590CED1491A8F03F35 /* Pods-BuildaKit.debug.xcconfig */; @@ -2126,6 +2164,7 @@ baseConfigurationReference = AFE44BFFCEFA7E33318AFA65 /* Pods-BuildaGitServer.release.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2219,16 +2258,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 3A7F0CC61BC14E640079A511 /* Build configuration list for PBXNativeTarget "BuildasaurUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7F0CC31BC14E640079A511 /* Debug */, - 3A7F0CC41BC14E640079A511 /* Testing */, - 3A7F0CC51BC14E640079A511 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 3A81BB331B5A77E9004732CD /* Build configuration list for PBXNativeTarget "BuildaKit" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Buildasaur/AppDelegate.swift b/Buildasaur/AppDelegate.swift index 503961c..fb940c5 100644 --- a/Buildasaur/AppDelegate.swift +++ b/Buildasaur/AppDelegate.swift @@ -26,6 +26,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { var syncerManager: SyncerManager! let menuItemManager = MenuItemManager() + let serviceAuthenticator = ServiceAuthenticator() var storyboardLoader: StoryboardLoader! @@ -40,6 +41,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { #else self.setup() #endif + + } func setup() { @@ -49,6 +52,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { // defs.setBool(true, forKey: "NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints") // defs.synchronize() + self.setupURLCallback() self.setupPersistence() self.storyboardLoader = StoryboardLoader(storyboard: NSStoryboard.mainStoryboard) @@ -63,6 +67,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { self.dashboardWindow = self.windowForPresentableViewControllerWithIdentifier("dashboard")!.0 } + func migratePersistence(persistence: Persistence) { let fileManager = NSFileManager.defaultManager() @@ -70,19 +75,19 @@ class AppDelegate: NSObject, NSApplicationDelegate { let migrator = CompositeMigrator(persistence: persistence) if migrator.isMigrationRequired() { - Log.info("Migration required, launching migrator") + print("Migration required, launching migrator") do { try migrator.attemptMigration() } catch { - Log.error("Migration failed with error \(error), wiping folder...") + print("Migration failed with error \(error), wiping folder...") //wipe the persistence. start over if we failed to migrate _ = try? fileManager.removeItemAtURL(persistence.readingFolder) } - Log.info("Migration finished") + print("Migration finished") } else { - Log.verbose("No migration necessary, skipping...") + print("No migration necessary, skipping...") } } @@ -90,15 +95,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { let persistence = PersistenceFactory.createStandardPersistence() - //setup logging - Logging.setup(persistence, alsoIntoFile: true) - //migration self.migratePersistence(persistence) + //setup logging + Logging.setup(persistence, alsoIntoFile: true) + //create storage manager let storageManager = StorageManager(persistence: persistence) let factory = SyncerFactory() + factory.syncerLifetimeChangeObserver = storageManager let loginItem = LoginItem() let syncerManager = SyncerManager(storageManager: storageManager, factory: factory, loginItem: loginItem) self.syncerManager = syncerManager @@ -123,9 +129,19 @@ class AppDelegate: NSObject, NSApplicationDelegate { let dashboard: DashboardViewController = self.storyboardLoader .presentableViewControllerWithStoryboardIdentifier("dashboardViewController", uniqueIdentifier: "dashboard", delegate: self) dashboard.syncerManager = self.syncerManager + dashboard.serviceAuthenticator = self.serviceAuthenticator return dashboard } + func handleUrl(url: NSURL) { + + print("Handling incoming url") + + if url.host == "oauth-callback" { + self.serviceAuthenticator.handleUrl(url) + } + } + func applicationShouldHandleReopen(sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { self.showMainWindow() @@ -173,6 +189,23 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } +extension AppDelegate { + + func setupURLCallback() { + + // listen to scheme url + NSAppleEventManager.sharedAppleEventManager().setEventHandler(self, andSelector:"handleGetURLEvent:withReplyEvent:", forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL)) + } + + func handleGetURLEvent(event: NSAppleEventDescriptor!, withReplyEvent: NSAppleEventDescriptor!) { + if let urlString = event.paramDescriptorForKeyword(AEKeyword(keyDirectObject))?.stringValue, url = NSURL(string: urlString) { + + //handle url + self.handleUrl(url) + } + } +} + extension AppDelegate: PresentableViewControllerDelegate { func configureViewController(viewController: PresentableViewController) { diff --git a/Buildasaur/Base.lproj/Main.storyboard b/Buildasaur/Base.lproj/Main.storyboard index 8083c15..08ae70f 100644 --- a/Buildasaur/Base.lproj/Main.storyboard +++ b/Buildasaur/Base.lproj/Main.storyboard @@ -877,7 +877,7 @@