-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #188 from czechboy0/hd/parsing_git_metadata_directly
Parsing metadata information directly from git repo (in the absence of checkout files)
- Loading branch information
Showing
8 changed files
with
266 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// | ||
// BlueprintFileParser.swift | ||
// Buildasaur | ||
// | ||
// Created by Honza Dvorsky on 10/21/15. | ||
// Copyright © 2015 Honza Dvorsky. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import BuildaUtils | ||
|
||
class BlueprintFileParser: SourceControlFileParser { | ||
|
||
func supportedFileExtensions() -> [String] { | ||
return ["xcscmblueprint"] | ||
} | ||
|
||
func parseFileAtUrl(url: NSURL) throws -> WorkspaceMetadata { | ||
|
||
//JSON -> NSDictionary | ||
let data = try NSData(contentsOfURL: url, options: NSDataReadingOptions()) | ||
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) | ||
guard let dictionary = jsonObject as? NSDictionary else { throw Error.withInfo("Failed to parse \(url)") } | ||
|
||
//parse our required keys | ||
let projectName = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintNameKey") | ||
let projectPath = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey") | ||
let projectWCCIdentifier = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey") | ||
|
||
var primaryRemoteRepositoryDictionary: NSDictionary? | ||
if let wccId = projectWCCIdentifier { | ||
if let wcConfigs = dictionary["DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey"] as? [NSDictionary] { | ||
primaryRemoteRepositoryDictionary = wcConfigs.filter({ | ||
if let loopWccId = $0.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey") { | ||
return loopWccId == wccId | ||
} | ||
return false | ||
}).first | ||
} | ||
} | ||
|
||
let projectURLString = primaryRemoteRepositoryDictionary?.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey") | ||
|
||
var projectWCCName: String? | ||
if | ||
let copyPaths = dictionary["DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey"] as? [String: String], | ||
let primaryRemoteRepoId = projectWCCIdentifier | ||
{ | ||
projectWCCName = copyPaths[primaryRemoteRepoId] | ||
} | ||
|
||
return try WorkspaceMetadata(projectName: projectName, projectPath: projectPath, projectWCCIdentifier: projectWCCIdentifier, projectWCCName: projectWCCName, projectURLString: projectURLString) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// | ||
// CheckoutFileParser.swift | ||
// Buildasaur | ||
// | ||
// Created by Honza Dvorsky on 10/21/15. | ||
// Copyright © 2015 Honza Dvorsky. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import BuildaUtils | ||
|
||
class CheckoutFileParser: SourceControlFileParser { | ||
|
||
func supportedFileExtensions() -> [String] { | ||
return ["xccheckout"] | ||
} | ||
|
||
func parseFileAtUrl(url: NSURL) throws -> WorkspaceMetadata { | ||
|
||
//plist -> NSDictionary | ||
guard let dictionary = NSDictionary(contentsOfURL: url) else { throw Error.withInfo("Failed to parse \(url)") } | ||
|
||
//parse our required keys | ||
let projectName = dictionary.optionalStringForKey("IDESourceControlProjectName") | ||
let projectPath = dictionary.optionalStringForKey("IDESourceControlProjectPath") | ||
let projectWCCIdentifier = dictionary.optionalStringForKey("IDESourceControlProjectWCCIdentifier") | ||
let projectWCCName = { () -> String? in | ||
if let wccId = projectWCCIdentifier { | ||
if let wcConfigs = dictionary["IDESourceControlProjectWCConfigurations"] as? [NSDictionary] { | ||
if let foundConfig = wcConfigs.filter({ | ||
if let loopWccId = $0.optionalStringForKey("IDESourceControlWCCIdentifierKey") { | ||
return loopWccId == wccId | ||
} | ||
return false | ||
}).first { | ||
//so much effort for this little key... | ||
return foundConfig.optionalStringForKey("IDESourceControlWCCName") | ||
} | ||
} | ||
} | ||
return nil | ||
}() | ||
let projectURLString = { dictionary.optionalStringForKey("IDESourceControlProjectURL") }() | ||
|
||
return try WorkspaceMetadata(projectName: projectName, projectPath: projectPath, projectWCCIdentifier: projectWCCIdentifier, projectWCCName: projectWCCName, projectURLString: projectURLString) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// | ||
// GitRepoMetadataParser.swift | ||
// Buildasaur | ||
// | ||
// Created by Honza Dvorsky on 10/21/15. | ||
// Copyright © 2015 Honza Dvorsky. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import BuildaUtils | ||
import CryptoSwift | ||
|
||
class GitRepoMetadataParser: SourceControlFileParser { | ||
|
||
func supportedFileExtensions() -> [String] { | ||
return [] //takes anything | ||
} | ||
|
||
private typealias ScriptRun = (String) throws -> String | ||
|
||
private func parseOrigin(run: ScriptRun) throws -> String { | ||
|
||
//find the first origin ending with "(fetch)" | ||
let remotes = try run("git remote -v") | ||
let fetchRemote = remotes.split("\n").filter { $0.hasSuffix("(fetch)") }.first | ||
guard let remoteLine = fetchRemote else { | ||
throw Error.withInfo("No fetch remote found in \(remotes)") | ||
} | ||
|
||
//parse the fetch remote, which is | ||
//e.g. "origin\[email protected]:czechboy0/BuildaUtils.git (fetch)" | ||
let comps = remoteLine | ||
.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "\t ")) | ||
.filter { !$0.isEmpty } | ||
|
||
//we need at least 2 comps, take the second | ||
guard comps.count >= 2 else { | ||
throw Error.withInfo("Cannot parse origin url from components \(comps)") | ||
} | ||
|
||
let remote = comps[1] | ||
|
||
//we got it! | ||
return remote | ||
} | ||
|
||
private func parseProjectName(url: NSURL) throws -> String { | ||
|
||
//that's the name of the passed-in project/workspace (most of the times) | ||
let projectName = ((url.lastPathComponent ?? "") as NSString).stringByDeletingPathExtension | ||
|
||
guard !projectName.isEmpty else { | ||
throw Error.withInfo("Failed to parse project name from url \(url)") | ||
} | ||
return projectName | ||
} | ||
|
||
private func parseProjectPath(url: NSURL, run: ScriptRun) throws -> String { | ||
|
||
//relative path from the root of the git repo of the passed-in project | ||
//or workspace file | ||
let absolutePath = url.path! | ||
let relativePath = "git ls-tree --full-name --name-only HEAD \"\(absolutePath)\"" | ||
let outPath = try run(relativePath) | ||
let trimmed = outPath.trim() | ||
guard !trimmed.isEmpty else { | ||
throw Error.withInfo("Failed to detect relative path of project \(url), output: \(outPath)") | ||
} | ||
return trimmed | ||
} | ||
|
||
private func parseProjectWCCName(url: NSURL, projectPath: String) throws -> String { | ||
|
||
//this is the folder name containing the git repo | ||
//it's the folder name before the project path | ||
//e.g. if project path is b/c/hello.xcodeproj, and the whole path | ||
//to the project is /Users/me/a/b/c/hello.xcodeproj, the project wcc name | ||
//would be "a" | ||
|
||
var projectPathComponents = projectPath.split("/") | ||
var pathComponents = url.pathComponents ?? [] | ||
|
||
//delete from the end from both lists when components equal | ||
while projectPathComponents.count > 0 { | ||
if pathComponents.last == projectPathComponents.last { | ||
pathComponents.removeLast() | ||
projectPathComponents.removeLast() | ||
} else { | ||
throw Error.withInfo("Logic error in parsing project WCC name, url: \(url), projectPath: \(projectPath)") | ||
} | ||
} | ||
|
||
let containingFolder = pathComponents.last! + "/" | ||
return containingFolder | ||
} | ||
|
||
private func parseProjectWCCIdentifier(projectUrl: String) throws -> String { | ||
|
||
//something reproducible, but i can't figure out how Xcode generates this. | ||
//also - it doesn't matter, AFA it's unique | ||
let hashed = projectUrl.sha1().uppercaseString | ||
return hashed | ||
} | ||
|
||
func parseFileAtUrl(url: NSURL) throws -> WorkspaceMetadata { | ||
|
||
let run = { (script: String) throws -> String in | ||
|
||
let cd = "cd \"\(url.path!)\"" | ||
let all = [cd, script].joinWithSeparator("\n") | ||
let response = Script.runTemporaryScript(all) | ||
if response.terminationStatus != 0 { | ||
throw Error.withInfo("Parsing git repo metadata failed, executing \"\(all)\", status: \(response.terminationStatus), output: \(response.standardOutput), error: \(response.standardError)") | ||
} | ||
return response.standardOutput | ||
} | ||
|
||
let origin = try self.parseOrigin(run) | ||
let projectName = try self.parseProjectName(url) | ||
let projectPath = try self.parseProjectPath(url, run: run) | ||
let projectWCCName = try self.parseProjectWCCName(url, projectPath: projectPath) | ||
let projectWCCIdentifier = try self.parseProjectWCCIdentifier(origin) | ||
|
||
return try WorkspaceMetadata(projectName: projectName, projectPath: projectPath, projectWCCIdentifier: projectWCCIdentifier, projectWCCName: projectWCCName, projectURLString: origin) | ||
} | ||
} | ||
|
||
extension String { | ||
|
||
func split(separator: String) -> [String] { | ||
return self.componentsSeparatedByString(separator) | ||
} | ||
|
||
func trim() -> String { | ||
return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.