Skip to content

Commit 07a1e9a

Browse files
authored
Merge pull request #699 from aciidb0mb3r/workspace-unedit
Workspace unedit
2 parents 5cdfb2a + e516345 commit 07a1e9a

File tree

2 files changed

+114
-1
lines changed

2 files changed

+114
-1
lines changed

Sources/Commands/Workspace.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ public enum WorkspaceOperationError: Swift.Error {
2323
/// The repository has uncommited changes.
2424
case hasUncommitedChanges(repo: AbsolutePath)
2525

26+
/// The repository has unpushed changes.
27+
case hasUnpushedChanges(repo: AbsolutePath)
28+
2629
/// The dependency is already in edit mode.
2730
case dependencyAlreadyInEditMode
31+
32+
/// The dependency is not in edit mode.
33+
case dependencyNotInEditMode
2834
}
2935

3036
/// The delegate interface used by the workspace to report status information.
@@ -348,6 +354,43 @@ public class Workspace {
348354
try saveState()
349355
}
350356

357+
/// Ends the edit mode of a dependency which is in edit mode.
358+
///
359+
/// - Parameters:
360+
/// - dependency: The dependency to be unedited.
361+
/// - forceRemove: If true, the dependency will be unedited even if has
362+
/// unpushed and uncommited changes. Otherwise will throw respective errors.
363+
///
364+
/// - throws: WorkspaceOperationError
365+
func unedit(dependency: ManagedDependency, forceRemove: Bool) throws {
366+
// If the dependency isn't in edit mode, we can't unedit it.
367+
guard let basedOn = dependency.basedOn else {
368+
throw WorkspaceOperationError.dependencyNotInEditMode
369+
}
370+
// Form the edit working repo path.
371+
let path = editablesPath.appending(dependency.subpath)
372+
// Check for uncommited and unpushed changes if force removal is off.
373+
if !forceRemove {
374+
let workingRepo = try repositoryManager.provider.openCheckout(at: path)
375+
guard !workingRepo.hasUncommitedChanges() else {
376+
throw WorkspaceOperationError.hasUncommitedChanges(repo: path)
377+
}
378+
guard try !workingRepo.hasUnpushedCommits() else {
379+
throw WorkspaceOperationError.hasUnpushedChanges(repo: path)
380+
}
381+
}
382+
// Remove the editable checkout from disk.
383+
try removeFileTree(path)
384+
// If this was the last editable dependency, remove the editables directory too.
385+
if try localFileSystem.getDirectoryContents(editablesPath).isEmpty {
386+
try removeFileTree(editablesPath)
387+
}
388+
// Restore the dependency state.
389+
dependencyMap[dependency.repository] = basedOn
390+
// Save the state.
391+
try saveState()
392+
}
393+
351394
// MARK: Low-level Operations
352395

353396
/// Fetch a given `repository` and create a local checkout for it.

Tests/CommandsTests/WorkspaceTests.swift

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,9 @@ final class WorkspaceTests: XCTestCase {
415415
XCTAssertEqual(editedDependency.basedOn?.currentVersion, dependency.currentVersion)
416416
XCTAssertEqual(editedDependency.basedOn?.currentRevision, dependency.currentRevision)
417417

418+
let editRepoPath = workspace.editablesPath.appending(editedDependency.subpath)
418419
// Get the repo from edits path.
419-
let editRepo = GitRepository(path: workspace.editablesPath.appending(editedDependency.subpath))
420+
let editRepo = GitRepository(path: editRepoPath)
420421
// Ensure that the editable checkout's remote points to the original repo path.
421422
XCTAssertEqual(try editRepo.remotes()[0].url, manifestGraph.repo("A").url)
422423

@@ -431,6 +432,74 @@ final class WorkspaceTests: XCTestCase {
431432
let dependency = workspace.dependencyMap[RepositorySpecifier(url: aManifest.url)]!
432433
XCTAssert(dependency.isInEditableState)
433434
}
435+
436+
// We should be able to unedit the dependency.
437+
try workspace.unedit(dependency: editedDependency, forceRemove: false)
438+
XCTAssertEqual(getDependency(aManifest).isInEditableState, false)
439+
XCTAssertFalse(exists(editRepoPath))
440+
XCTAssertFalse(exists(workspace.editablesPath))
441+
}
442+
}
443+
444+
func testUneditDependency() throws {
445+
mktmpdir { path in
446+
let manifestGraph = try MockManifestGraph(at: path,
447+
rootDeps: [
448+
MockDependency("A", version: Version(1, 0, 0)..<Version(1, .max, .max)),
449+
],
450+
packages: [
451+
MockPackage("A", version: v1),
452+
MockPackage("A", version: nil), // To load the edited package manifest.
453+
]
454+
)
455+
// Create the workspace.
456+
let workspace = try Workspace(rootPackage: path, manifestLoader: manifestGraph.manifestLoader, delegate: TestWorkspaceDelegate())
457+
// Load the package graph.
458+
let graph = try workspace.loadPackageGraph()
459+
// Sanity checks.
460+
XCTAssertEqual(graph.packages.count, 2)
461+
XCTAssertEqual(graph.packages.map{ $0.name }.sorted(), ["A", "Root"])
462+
463+
let manifests = try workspace.loadDependencyManifests()
464+
guard let aManifest = manifests.lookup("A") else {
465+
return XCTFail("Expected manifest for package A not found")
466+
}
467+
func getDependency(_ manifest: Manifest) -> Workspace.ManagedDependency {
468+
return workspace.dependencyMap[RepositorySpecifier(url: manifest.url)]!
469+
}
470+
let dependency = getDependency(aManifest)
471+
// Put the dependency in edit mode.
472+
try workspace.edit(dependency: dependency, at: dependency.currentRevision!, packageName: aManifest.name)
473+
474+
let editedDependency = getDependency(aManifest)
475+
let editRepoPath = workspace.editablesPath.appending(editedDependency.subpath)
476+
// Write something in repo.
477+
try localFileSystem.writeFileContents(editRepoPath.appending(component: "test.txt"), bytes: "Hi")
478+
try systemQuietly([Git.tool, "-C", editRepoPath.asString, "add", "test.txt"])
479+
480+
// Try to unedit.
481+
do {
482+
try workspace.unedit(dependency: editedDependency, forceRemove: false)
483+
XCTFail("Unexpected edit success")
484+
} catch WorkspaceOperationError.hasUncommitedChanges(let repo) {
485+
XCTAssertEqual(repo, editRepoPath)
486+
}
487+
// Commit and try to unedit.
488+
// FIXME: Create proper utility for this.
489+
try systemQuietly([Git.tool, "-C", editRepoPath.asString, "config", "user.email", "[email protected]"])
490+
try systemQuietly([Git.tool, "-C", editRepoPath.asString, "config", "user.name", "Example Example"])
491+
try systemQuietly([Git.tool, "-C", editRepoPath.asString, "commit", "-m", "Add some files."])
492+
do {
493+
try workspace.unedit(dependency: editedDependency, forceRemove: false)
494+
XCTFail("Unexpected edit success")
495+
} catch WorkspaceOperationError.hasUnpushedChanges(let repo) {
496+
XCTAssertEqual(repo, editRepoPath)
497+
}
498+
// Force remove.
499+
try workspace.unedit(dependency: editedDependency, forceRemove: true)
500+
XCTAssertEqual(getDependency(aManifest).isInEditableState, false)
501+
XCTAssertFalse(exists(editRepoPath))
502+
XCTAssertFalse(exists(workspace.editablesPath))
434503
}
435504
}
436505

@@ -441,6 +510,7 @@ final class WorkspaceTests: XCTestCase {
441510
("testPackageGraphLoadingBasics", testPackageGraphLoadingBasics),
442511
("testPackageGraphLoadingWithCloning", testPackageGraphLoadingWithCloning),
443512
("testUpdate", testUpdate),
513+
("testUneditDependency", testUneditDependency),
444514
("testCleanAndReset", testCleanAndReset),
445515
]
446516
}

0 commit comments

Comments
 (0)