 	}
 }
 
+private func ==(lhs: DependencyGraph, rhs: DependencyGraph) -> Bool {
+	if lhs.edges.count != rhs.edges.count || lhs.roots.count != rhs.roots.count {
+		return false
+	}
+
+	for (edge, leftDeps) in lhs.edges {
+		if let rightDeps = rhs.edges[edge] {
+			if leftDeps.count != rightDeps.count {
+				return false
+			}
+
+			for (dep, _) in leftDeps {
+				if rightDeps[dep] == nil {
+					return false
+				}
+			}
+		} else {
+			return false
+		}
+	}
+
+	for (root, _) in lhs.roots {
+		if rhs.roots[root] == nil {
+			return false
+		}
+	}
+
+	return true
+}
+
+extension DependencyGraph: Printable {
+	private var description: String {
+		var str = "Roots:"
+
+		for (root, _) in roots {
+			str += "\n\t\(root)"
+		}
+
+		str += "\n\nEdges:"
+
+		for (node, dependencies) in edges {
+			str += "\n\t\(node.identifier) ->"
+			for (dep, _) in dependencies {
+				str += "\n\t\t\(dep)"
+			}
+		}
+
+		return str
+	}
+}
+
+/// Attempts to unify two graphs.
+///
+/// Returns the new graph, or an error if the graphs specify inconsistent
+/// versions for one or more dependencies.
 private func mergeGraphs(lhs: DependencyGraph, rhs: DependencyGraph) -> Result<DependencyGraph> {
 	var result = success(lhs)
 

			.merge(identity)
	}

	private func nodePermutationsForCartfile(cartfile: Cartfile) -> ColdSignal<[DependencyNode]> {
		let nodeSignals = cartfile.dependencies.map { dependency -> ColdSignal<DependencyNode> in
			// TODO: If this signal is empty (because no version meets the

	return result
}

/// A node in, or being considered for, an acyclic dependency graph.
private class DependencyNode: Comparable {
	/// The dependency that this node refers to.
	let identifier: DependencyIdentifier

	/// The version of the dependency that this node represents.
	///
	/// This version is merely "proposed" because it depends on the final
	/// resolution of the graph, as well as whether any "better" graphs exist.
	let proposedVersion: SemanticVersion

	/// The current requirements applied to this dependency.
	///
	/// This specifier may change as the graph is added to, and the requirements
	/// become more stringent.
	var versionSpecifier: VersionSpecifier

	/// A DependencyVersion equivalent to this node.
	var dependencyVersion: DependencyVersion<SemanticVersion> {
		return DependencyVersion(identifier: identifier, version: proposedVersion)
	}

	init(identifier: DependencyIdentifier, proposedVersion: SemanticVersion, versionSpecifier: VersionSpecifier) {
		precondition(versionSpecifier.satisfiedBy(proposedVersion))

		self.identifier = identifier
		self.proposedVersion = proposedVersion
		self.versionSpecifier = versionSpecifier
	}
}

private func <(lhs: DependencyNode, rhs: DependencyNode) -> Bool {
	// Try higher versions first.
	return lhs.proposedVersion > rhs.proposedVersion
}

private func ==(lhs: DependencyNode, rhs: DependencyNode) -> Bool {
	return lhs.identifier == rhs.identifier
}

extension DependencyNode: Hashable {
	private var hashValue: Int {
		return identifier.hashValue
	}
}

extension DependencyNode: Printable {
	private var description: String {
		return "\(identifier) @ \(proposedVersion) (restricted to \(versionSpecifier))"
	}
}
