diff --git a/Makefile b/Makefile
index 94574ba..f77e193 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
# By default export all variables
export
-.PHONY: install release debug build setup clean
+.PHONY: install release debug build setup clean test
PROJECT ?= 'modulo.xcodeproj'
SCHEME ?= 'modulo'
@@ -11,6 +11,9 @@ CONFIGURATION ?= 'Debug'
# Build for debugging
debug: build
+test:
+ xcodebuild -project $(PROJECT) -scheme ModuloKit test
+
# Install `modulo` to `/usr/local/bin`
install: release
cp $(SYMROOT)/Release/modulo /usr/local/bin/
diff --git a/Modules/ELCLI/ELCLI/CLI.swift b/Modules/ELCLI/ELCLI/CLI.swift
index 84a3a70..b320799 100755
--- a/Modules/ELCLI/ELCLI/CLI.swift
+++ b/Modules/ELCLI/ELCLI/CLI.swift
@@ -154,7 +154,20 @@ open class CLI {
// it's a "--flag value" type argument.
if index < arguments.count - 1 {
value = arguments[index + 1]
- skipNext = true
+ // If we're assuming `--flag value` make sure
+ // our `value` isn't a stop marker or flag. If it is
+ // our value should instead be `nil`'d out so our
+ // command does not get a value where it is our next
+ // argument.
+ if let argValue = value,
+ isStopMarker(argValue) || isFlag(argValue) {
+ value = nil
+ } else {
+ // However if that's not the case we should skip
+ // the next command because we really did get
+ // `--flag value`
+ skipNext = true
+ }
}
}
}
diff --git a/ModuloKit/Actions.swift b/ModuloKit/Actions.swift
index faadac5..98244de 100644
--- a/ModuloKit/Actions.swift
+++ b/ModuloKit/Actions.swift
@@ -22,8 +22,8 @@ open class Actions {
}
}
- open func addDependency(_ url: String, version: SemverRange?, unmanaged: Bool) -> ErrorCode {
- let dep = DependencySpec(repositoryURL: url, version: version)
+ open func addDependency(_ url: String, version: SemverRange?, unmanagedValue: String?, unmanaged: Bool) -> ErrorCode {
+ let dep = DependencySpec(repositoryURL: url, version: version, unmanagedValue: unmanagedValue)
if var workingSpec = ModuleSpec.workingSpec() {
// does this dep already exist in here??
if let _ = workingSpec.dependencyForURL(url) {
@@ -77,6 +77,16 @@ open class Actions {
exit(checkoutResult.errorMessage())
}
}
+ if let unmanagedValue = dep.unmanagedValue {
+ let checkoutResult = scm.checkout(branchOrHash: unmanagedValue, path: clonePath)
+ if checkoutResult != .success {
+ exit(checkoutResult.errorMessage())
+ }
+ let pullResult = scm.pull(clonePath, remoteData: nil)
+ if pullResult != .success {
+ exit(pullResult.errorMessage())
+ }
+ }
// things worked, so add it to the approprate place in the overall state.
if explicit {
@@ -92,7 +102,7 @@ open class Actions {
}
// if they're unmanaged and on a branch, tracking a remote, just do a pull
- if dep.unmanaged == true, let currentBranch = scm.branchName(clonePath) {
+ if dep.unmanaged == true && dep.unmanagedValue == nil, let currentBranch = scm.branchName(clonePath) {
if scm.remoteTrackingBranch(currentBranch) != nil {
let pullResult = scm.pull(clonePath, remoteData: nil)
if pullResult != .success {
@@ -106,8 +116,13 @@ open class Actions {
if checkoutResult != .success {
exit(checkoutResult.errorMessage())
}
+ } else if let unmanagedValue = dep.unmanagedValue {
+ let checkoutResult = scm.checkout(branchOrHash: unmanagedValue, path: clonePath)
+ if checkoutResult != .success {
+ exit(checkoutResult.errorMessage())
+ }
} else {
- exit("\(dep.name()) doesn't have a version and isn't unmanaged, not sure what to do.")
+ exit("\(dep.name()) doesn't have a version and isn't marked as 'unmanaged', not sure what to do.")
}
}
}
diff --git a/ModuloKit/Commands/AddCommand.swift b/ModuloKit/Commands/AddCommand.swift
index 66de609..599e004 100644
--- a/ModuloKit/Commands/AddCommand.swift
+++ b/ModuloKit/Commands/AddCommand.swift
@@ -19,6 +19,7 @@ open class AddCommand: NSObject, Command {
fileprivate var repositoryURL: String! = nil
fileprivate var shouldUpdate: Bool = false
fileprivate var unmanaged: Bool = false
+ fileprivate var unmanagedValue: String? = nil
// Protocol conformance
open var name: String { return "add" }
@@ -41,8 +42,9 @@ open class AddCommand: NSObject, Command {
}
}
- addOption(["--unmanaged"], usage: "specifies that this module will be unmanaged") { (option, value) in
+ addOptionValue(["--unmanaged"], usage: "specifies that this module will be unmanaged", valueSignature: "<[hash|branch|nothing]>") { (option, value) -> Void in
self.unmanaged = true
+ self.unmanagedValue = value
}
addOption(["-u", "--update"], usage: "performs the update command after adding a module") { (option, value) in
@@ -58,7 +60,7 @@ open class AddCommand: NSObject, Command {
let actions = Actions()
if version == nil && unmanaged == false {
- writeln(.stderr, "A version or range must be specified via --version, or --unmanaged must be used.")
+ writeln(.stderr, "A version or range must be specified via --version or --unmanaged must be used.")
return ErrorCode.commandError.rawValue
}
@@ -69,7 +71,7 @@ open class AddCommand: NSObject, Command {
}
}
- let result = actions.addDependency(repositoryURL, version: version, unmanaged: unmanaged)
+ let result = actions.addDependency(repositoryURL, version: version, unmanagedValue: unmanagedValue, unmanaged: unmanaged)
if result == .success {
if shouldUpdate {
writeln(.stdout, "Added \(String(describing: repositoryURL)).")
diff --git a/ModuloKit/SCM/Git.swift b/ModuloKit/SCM/Git.swift
index f1fa95a..c4bd287 100644
--- a/ModuloKit/SCM/Git.swift
+++ b/ModuloKit/SCM/Git.swift
@@ -100,7 +100,7 @@ open class Git: SCM {
let initialWorkingPath = FileManager.workingPath()
FileManager.setWorkingPath(path)
- let updateCommand = "git fetch --recurse-submodules --all --tags"
+ let updateCommand = "git fetch --recurse-submodules --all"
let status = runCommand(updateCommand)
FileManager.setWorkingPath(initialWorkingPath)
@@ -175,6 +175,49 @@ open class Git: SCM {
return .success
}
+
+ open func checkout(branchOrHash: String, path: String) -> SCMResult {
+ if !FileManager.fileExists(path) {
+ return .error(code: 1, message: "Module path '\(path)' does not exist.")
+ }
+
+ var checkoutCommand = ""
+
+ let initialWorkingPath = FileManager.workingPath()
+ FileManager.setWorkingPath(path)
+
+ // try fetching it directly
+ let fetchResult = runCommand("git fetch origin \(branchOrHash)")
+
+ if branches(".").contains(branchOrHash) {
+ checkoutCommand = "git checkout origin/\(branchOrHash) --quiet"
+ } else {
+ checkoutCommand = "git checkout \(branchOrHash) --quiet"
+ }
+
+ if fetchResult != 0 {
+ if verbose {
+ writeln(.stderr, "Unable to find unmanaged value '\(branchOrHash)'.")
+ }
+ return .error(code: SCMDefaultError, message: "Unable to find a match for \(branchOrHash).")
+ }
+
+ let status = runCommand(checkoutCommand)
+
+ let submodulesResult = collectAnySubmodules()
+
+ FileManager.setWorkingPath(initialWorkingPath)
+
+ if status != 0 {
+ return .error(code: status, message: "Unable to checkout '\(branchOrHash)'.")
+ }
+
+ if submodulesResult != .success {
+ return submodulesResult
+ }
+
+ return .success
+ }
open func adjustIgnoreFile(pattern: String, removing: Bool) -> SCMResult {
let localModulesPath = State.instance.modulePathName
diff --git a/ModuloKit/SCM/SCM.swift b/ModuloKit/SCM/SCM.swift
index 5f130a4..710a859 100644
--- a/ModuloKit/SCM/SCM.swift
+++ b/ModuloKit/SCM/SCM.swift
@@ -76,6 +76,9 @@ public protocol SCM {
func fetch(_ path: String) -> SCMResult
func pull(_ path: String, remoteData: String?) -> SCMResult
func checkout(version: SemverRange, path: String) -> SCMResult
+ /// Check out an arbitrary point or the HEAD of a branch (in git)
+ /// or the equivalent in other SCM solutions
+ func checkout(branchOrHash: String, path: String) -> SCMResult
func remove(_ path: String) -> SCMResult
func adjustIgnoreFile(pattern: String, removing: Bool) -> SCMResult
func checkStatus(_ path: String) -> SCMResult
diff --git a/ModuloKit/Specs/DependencySpec.swift b/ModuloKit/Specs/DependencySpec.swift
index 03f8f63..8ff1b0c 100644
--- a/ModuloKit/Specs/DependencySpec.swift
+++ b/ModuloKit/Specs/DependencySpec.swift
@@ -17,6 +17,9 @@ public struct DependencySpec {
var repositoryURL: String
// version or version range
var version: SemverRange?
+ /// Optional unmanaged property to track
+ /// such as a branch name, commit hash, or nothing
+ var unmanagedValue: String?
var unmanaged: Bool {
get {
@@ -29,7 +32,8 @@ extension DependencySpec: ELDecodable {
public static func decode(_ json: JSON?) throws -> DependencySpec {
return try DependencySpec(
repositoryURL: json ==> "repositoryURL",
- version: json ==> "version"
+ version: json ==> "version",
+ unmanagedValue: json ==> "unmanagedValue"
)
}
@@ -42,7 +46,8 @@ extension DependencySpec: ELEncodable {
public func encode() throws -> JSON {
return try encodeToJSON([
"repositoryURL" <== repositoryURL,
- "version" <== version
+ "version" <== version,
+ "unmanagedValue" <== unmanagedValue
])
}
}
diff --git a/ModuloKitTests/TestAdd.swift b/ModuloKitTests/TestAdd.swift
index fcc41ee..113fe3a 100644
--- a/ModuloKitTests/TestAdd.swift
+++ b/ModuloKitTests/TestAdd.swift
@@ -78,4 +78,61 @@ class TestAdd: XCTestCase {
FileManager.setWorkingPath("..")
}
+
+ func testAddUnmanagedModuleWithBranch() {
+ let status = Git().clone("git@github.com:modulo-dm/test-add.git", path: "test-add")
+ XCTAssertTrue(status == .success)
+
+ FileManager.setWorkingPath("test-add")
+
+ let repoURL = "git@github.com:modulo-dm/test-add-update.git"
+
+ let result = Modulo.run(["add", repoURL, "--unmanaged", "master", "-v"])
+ XCTAssertTrue(result == .success)
+
+
+ guard let spec = ModuleSpec.load(contentsOfFile: specFilename) else {
+ XCTFail("Failed to get spec from file \(specFilename)")
+ return }
+ XCTAssertTrue(spec.dependencies.count > 0)
+ guard let dep = spec.dependencyForURL(repoURL) else {
+ XCTFail("Failed to find dependency for url \(repoURL) in spec \(spec)")
+ return }
+ XCTAssertNil(dep.version)
+ XCTAssertTrue(dep.unmanaged)
+ XCTAssertNotNil(dep.unmanagedValue)
+ XCTAssertTrue(dep.unmanagedValue == "master")
+
+ FileManager.setWorkingPath("..")
+
+ Git().remove("test-add")
+ }
+
+ func testAddModuleUnmanagedNoArgs() {
+ let status = Git().clone("git@github.com:modulo-dm/test-add.git", path: "test-add")
+ XCTAssertTrue(status == .success)
+
+ FileManager.setWorkingPath("test-add")
+
+ let repoURL = "git@github.com:modulo-dm/test-add-update.git"
+
+ let result = Modulo.run(["add", repoURL, "--unmanaged", "-v"])
+ XCTAssertTrue(result == .success)
+
+
+ guard let spec = ModuleSpec.load(contentsOfFile: specFilename) else {
+ XCTFail("Failed to get spec from file \(specFilename)")
+ return }
+ XCTAssertTrue(spec.dependencies.count > 0)
+ guard let dep = spec.dependencyForURL(repoURL) else {
+ XCTFail("Failed to find dependency for url \(repoURL) in spec \(spec)")
+ return }
+ XCTAssertNil(dep.version)
+ XCTAssertTrue(dep.unmanaged)
+ XCTAssertNil(dep.unmanagedValue)
+
+ FileManager.setWorkingPath("..")
+
+ Git().remove("test-add")
+ }
}
diff --git a/ModuloKitTests/TestDummyApp.swift b/ModuloKitTests/TestDummyApp.swift
index 871646c..1259c23 100644
--- a/ModuloKitTests/TestDummyApp.swift
+++ b/ModuloKitTests/TestDummyApp.swift
@@ -41,7 +41,7 @@ class TestDummyApp: XCTestCase {
XCTAssertTrue(spec!.dependencyForURL("git@github.com:modulo-dm/test-add-update.git") != nil)
let checkedOut = Git().branchName("modules/test-add-update")
- XCTAssertTrue(checkedOut == "master")
+ XCTAssertTrue(checkedOut == "master", "checkedOut should have been 'master' but was '\(String(describing: checkedOut))'")
XCTAssertTrue(FileManager.fileExists("modules/test-add-update"))
XCTAssertTrue(FileManager.fileExists("modules/test-dep1"))
diff --git a/modulo.xcodeproj/xcshareddata/xcschemes/modulo.xcscheme b/modulo.xcodeproj/xcshareddata/xcschemes/modulo.xcscheme
index b00a1fb..19a2b13 100644
--- a/modulo.xcodeproj/xcshareddata/xcschemes/modulo.xcscheme
+++ b/modulo.xcodeproj/xcshareddata/xcschemes/modulo.xcscheme
@@ -69,11 +69,11 @@
+ isEnabled = "NO">
+ isEnabled = "YES">