Skip to content

Commit

Permalink
Implement submodule addition for checkout
Browse files Browse the repository at this point in the history
  • Loading branch information
jspahrsummers committed Nov 20, 2014
1 parent 9119c2f commit 4e68915
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 34 deletions.
41 changes: 38 additions & 3 deletions CarthageKit/Git.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,15 @@ public func launchGitTask(arguments: [String], repositoryFileURL: NSURL? = nil,
}

/// Returns a signal that completes when cloning completes successfully.
public func cloneRepository(cloneURL: GitURL, destinationURL: NSURL) -> ColdSignal<String> {
public func cloneRepository(cloneURL: GitURL, destinationURL: NSURL, bare: Bool = true) -> ColdSignal<String> {
precondition(destinationURL.fileURL)

return launchGitTask([ "clone", "--bare", "--quiet", cloneURL.URLString, destinationURL.path! ])
var arguments = [ "clone" ]
if bare {
arguments.append("--bare")
}

return launchGitTask(arguments + [ "--quiet", cloneURL.URLString, destinationURL.path! ])
}

/// Returns a signal that completes when the fetch completes successfully.
Expand Down Expand Up @@ -271,7 +276,7 @@ public func cloneSubmoduleInWorkingDirectory(submodule: Submodule, workingDirect
return .error(error ?? CarthageError.RepositoryCheckoutFailed(workingDirectoryURL: submoduleDirectoryURL, reason: "could not remove submodule checkout").error)
}

return launchGitTask([ "clone", "--quiet", submodule.URL.URLString, submodule.path ], repositoryFileURL: workingDirectoryURL)
return cloneRepository(submodule.URL, workingDirectoryURL.URLByAppendingPathComponent(submodule.path), bare: false)
}
.then(launchGitTask([ "checkout", "--quiet", submodule.SHA ], repositoryFileURL: submoduleDirectoryURL))
// Clone nested submodules in a separate step, to quiet its output correctly.
Expand Down Expand Up @@ -372,3 +377,33 @@ public func commitExistsInRepository(repositoryFileURL: NSURL, revision: String
.catch { _ in .single(false) }
}
}

/// Adds the given submodule to the given repository, cloning from `fetchURL` if
/// the desired revision does not exist or the submodule needs to be cloned.
public func addSubmoduleToRepository(repositoryFileURL: NSURL, submodule: Submodule, fetchURL: GitURL) -> ColdSignal<()> {
let submoduleDirectoryURL = repositoryFileURL.URLByAppendingPathComponent(submodule.path, isDirectory: true)

let checkoutSubmodule = ColdSignal<()>.lazy {
return launchGitTask([ "checkout", "--quiet", submodule.SHA ], repositoryFileURL: submoduleDirectoryURL)
.then(.empty())
}

return ColdSignal.lazy {
let submoduleGitPath = submoduleDirectoryURL.URLByAppendingPathComponent(".git").path!
if NSFileManager.defaultManager().fileExistsAtPath(submoduleGitPath) {
// If the submodule repository already exists, just check out and
// stage the correct revision.
return fetchRepository(submoduleDirectoryURL, remoteURL: fetchURL)
.then(checkoutSubmodule)
.then(launchGitTask([ "add", "--force", submodule.path ], repositoryFileURL: repositoryFileURL))
.then(.empty())
} else {
// If it doesn't exist, clone and initialize a submodule from our
// local bare repository.
return cloneRepository(fetchURL, submoduleDirectoryURL, bare: false)
.then(launchGitTask([ "submodule", "--quiet", "add", "--force", "--name", submodule.name, "--", submodule.URL.URLString, submodule.path ], repositoryFileURL: repositoryFileURL))
.then(launchGitTask([ "submodule", "--quiet", "init", "--", submodule.path ], repositoryFileURL: repositoryFileURL))
.then(.empty())
}
}
}
79 changes: 48 additions & 31 deletions CarthageKit/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,47 +157,58 @@ public final class Project {
/// A scheduler used to serialize all Git operations within this project.
private let gitOperationScheduler = QueueScheduler()

/// Runs the given Git operation, blocking the `gitOperationScheduler` until
/// it has completed.
private func runGitOperation<T>(operation: ColdSignal<T>) -> ColdSignal<T> {
return ColdSignal { subscriber in
let schedulerDisposable = self.gitOperationScheduler.schedule {
let results = operation
.reduce(initial: []) { $0 + [ $1 ] }
.first()

switch results {
case let .Success(values):
ColdSignal.fromValues(values.unbox).start(subscriber)

case let .Failure(error):
subscriber.put(.Error(error))
}
}

subscriber.disposable.addDisposable(schedulerDisposable)
}.deliverOn(QueueScheduler())
}

/// Clones the given project to the global repositories folder, or fetches
/// inside it if it has already been cloned.
///
/// Returns a signal which will send the URL to the repository's folder on
/// disk.
private func cloneOrFetchProject(project: ProjectIdentifier) -> ColdSignal<NSURL> {
let repositoryURL = repositoryFileURLForProject(project)
let operation = ColdSignal<NSURL>.lazy {
var error: NSError?
if !NSFileManager.defaultManager().createDirectoryAtURL(CarthageDependencyRepositoriesURL, withIntermediateDirectories: true, attributes: nil, error: &error) {
return .error(error ?? CarthageError.WriteFailed(CarthageDependencyRepositoriesURL).error)
}

return ColdSignal { subscriber in
let schedulerDisposable = self.gitOperationScheduler.schedule {
var error: NSError?
if !NSFileManager.defaultManager().createDirectoryAtURL(CarthageDependencyRepositoriesURL, withIntermediateDirectories: true, attributes: nil, error: &error) {
subscriber.put(.Error(error ?? CarthageError.WriteFailed(CarthageDependencyRepositoriesURL).error))
return
}

let remoteURL = self.repositoryURLForProject(project)
var result: Result<()>?

if NSFileManager.defaultManager().createDirectoryAtURL(repositoryURL, withIntermediateDirectories: false, attributes: nil, error: nil) {
// If we created the directory, we're now responsible for
// cloning it.
self._projectEventsSink.put(.Cloning(project))
result = cloneRepository(remoteURL, repositoryURL).wait()
} else {
self._projectEventsSink.put(.Fetching(project))
result = fetchRepository(repositoryURL, remoteURL: remoteURL).wait()
}
let remoteURL = self.repositoryURLForProject(project)
if NSFileManager.defaultManager().createDirectoryAtURL(repositoryURL, withIntermediateDirectories: false, attributes: nil, error: nil) {
// If we created the directory, we're now responsible for
// cloning it.
self._projectEventsSink.put(.Cloning(project))

switch result! {
case .Success:
subscriber.put(.Next(Box(repositoryURL)))
subscriber.put(.Completed)
return cloneRepository(remoteURL, repositoryURL)
.then(.single(repositoryURL))
} else {
self._projectEventsSink.put(.Fetching(project))

case let .Failure(error):
subscriber.put(.Error(error))
}
return fetchRepository(repositoryURL, remoteURL: remoteURL)
.then(.single(repositoryURL))
}
}

subscriber.disposable.addDisposable(schedulerDisposable)
}.deliverOn(QueueScheduler())
return runGitOperation(operation)
}

/// Sends all versions available for the given project.
Expand Down Expand Up @@ -271,7 +282,14 @@ public final class Project {
let repositoryURL = self.repositoryFileURLForProject(project)
let workingDirectoryURL = self.directoryURL.URLByAppendingPathComponent(project.relativePath, isDirectory: true)

let checkoutSignal = checkoutRepositoryToDirectory(repositoryURL, workingDirectoryURL, revision: revision)
let checkoutSignal = ColdSignal<()>.lazy {
if self.useSubmodules {
let submodule = Submodule(name: project.relativePath, path: project.relativePath, URL: self.repositoryURLForProject(project), SHA: revision)
return self.runGitOperation(addSubmoduleToRepository(self.directoryURL, submodule, GitURL(repositoryURL.path!)))
} else {
return checkoutRepositoryToDirectory(repositoryURL, workingDirectoryURL, revision: revision)
}
}
.on(subscribed: {
self._projectEventsSink.put(.CheckingOut(project, revision))
})
Expand All @@ -286,7 +304,6 @@ public final class Project {
}
.merge(identity)
.then(checkoutSignal)
.then(.empty())
}

/// Checks out the dependencies listed in the project's Cartfile.lock.
Expand Down

0 comments on commit 4e68915

Please sign in to comment.