Skip to content

Commit 37f0f1a

Browse files
authored
Merge pull request #705 from aciidb0mb3r/add-branch-edit
[Workspace] Add option to checkout to a new branch when editing
2 parents e25aa0d + 7d4f28a commit 37f0f1a

File tree

2 files changed

+75
-1
lines changed

2 files changed

+75
-1
lines changed

Sources/Commands/Workspace.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public enum WorkspaceOperationError: Swift.Error {
3131

3232
/// The dependency is not in edit mode.
3333
case dependencyNotInEditMode
34+
35+
/// The branch already exists in repository.
36+
case branchAlreadyExists
3437
}
3538

3639
/// The delegate interface used by the workspace to report status information.
@@ -331,7 +334,15 @@ public class Workspace {
331334
}
332335

333336
/// Puts a dependency in edit mode creating a checkout in editables directory.
334-
func edit(dependency: ManagedDependency, at revision: Revision, packageName: String) throws {
337+
///
338+
/// - Parameters:
339+
/// - dependency: The dependency to put in edit mode.
340+
/// - revision: The revision at which the dependency checked out to.
341+
/// - packageName: The name of the package corresponding to the dependency. This is used for the checkout directory name.
342+
/// - checkoutBranch: If provided, a new branch with this name will be created from the revision provided.
343+
///
344+
/// - throws: WorkspaceOperationError
345+
func edit(dependency: ManagedDependency, at revision: Revision, packageName: String, checkoutBranch: String? = nil) throws {
335346
// Ensure that the dependency is not already in edit mode.
336347
guard !dependency.isInEditableState else {
337348
throw WorkspaceOperationError.dependencyAlreadyInEditMode
@@ -344,9 +355,21 @@ public class Workspace {
344355
// We should already have the handle if we're editing a dependency.
345356
assert(handle.isAvailable)
346357

358+
// If a branch is provided, make sure it isn't already present in the repository.
359+
if let branch = checkoutBranch {
360+
let repo = try handle.open()
361+
guard !repo.exists(revision: Revision(identifier: branch)) else {
362+
throw WorkspaceOperationError.branchAlreadyExists
363+
}
364+
}
365+
347366
try handle.cloneCheckout(to: path, editable: true)
348367
let workingRepo = try repositoryManager.provider.openCheckout(at: path)
349368
try workingRepo.checkout(revision: revision)
369+
// Checkout to the new branch if provided.
370+
if let branch = checkoutBranch {
371+
try workingRepo.checkout(newBranch: branch)
372+
}
350373

351374
// Change its stated to edited.
352375
dependencyMap[dependency.repository] = dependency.makingEditable(subpath: path.relative(to: editablesPath))

Tests/CommandsTests/WorkspaceTests.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,12 @@ final class WorkspaceTests: XCTestCase {
420420
let editRepo = GitRepository(path: editRepoPath)
421421
// Ensure that the editable checkout's remote points to the original repo path.
422422
XCTAssertEqual(try editRepo.remotes()[0].url, manifestGraph.repo("A").url)
423+
// Check revision and head.
424+
XCTAssertEqual(try editRepo.getCurrentRevision(), dependency.currentRevision!)
425+
// FIXME: Current checkout behavior seems wrong, it just resets and doesn't leave checkout to a detached head.
426+
#if false
427+
XCTAssertEqual(try popen([Git.tool, "-C", editRepoPath.asString, "rev-parse", "--abbrev-ref", "HEAD"]).chomp(), "HEAD")
428+
#endif
423429

424430
do {
425431
try workspace.edit(dependency: editedDependency, at: dependency.currentRevision!, packageName: aManifest.name)
@@ -441,6 +447,50 @@ final class WorkspaceTests: XCTestCase {
441447
}
442448
}
443449

450+
func testEditDependencyOnNewBranch() throws {
451+
mktmpdir { path in
452+
let manifestGraph = try MockManifestGraph(at: path,
453+
rootDeps: [
454+
MockDependency("A", version: Version(1, 0, 0)..<Version(1, .max, .max)),
455+
],
456+
packages: [
457+
MockPackage("A", version: v1),
458+
MockPackage("A", version: nil), // To load the edited package manifest.
459+
]
460+
)
461+
// Create the workspace.
462+
let workspace = try Workspace(rootPackage: path, manifestLoader: manifestGraph.manifestLoader, delegate: TestWorkspaceDelegate())
463+
// Load the package graph.
464+
let graph = try workspace.loadPackageGraph()
465+
let manifests = try workspace.loadDependencyManifests()
466+
guard let aManifest = manifests.lookup("A") else {
467+
return XCTFail("Expected manifest for package A not found")
468+
}
469+
func getDependency(_ manifest: Manifest) -> Workspace.ManagedDependency {
470+
return workspace.dependencyMap[RepositorySpecifier(url: manifest.url)]!
471+
}
472+
// Get the dependency for package A.
473+
let dependency = getDependency(aManifest)
474+
// Put the dependency in edit mode at its current revision on a new branch.
475+
try workspace.edit(dependency: dependency, at: dependency.currentRevision!, packageName: aManifest.name, checkoutBranch: "BugFix")
476+
let editedDependency = getDependency(aManifest)
477+
XCTAssert(editedDependency.isInEditableState)
478+
479+
let editRepoPath = workspace.editablesPath.appending(editedDependency.subpath)
480+
let editRepo = GitRepository(path: editRepoPath)
481+
XCTAssertEqual(try editRepo.getCurrentRevision(), dependency.currentRevision!)
482+
XCTAssertEqual(try popen([Git.tool, "-C", editRepoPath.asString, "rev-parse", "--abbrev-ref", "HEAD"]).chomp(), "BugFix")
483+
// Unedit it.
484+
try workspace.unedit(dependency: editedDependency, forceRemove: false)
485+
XCTAssertEqual(getDependency(aManifest).isInEditableState, false)
486+
487+
do {
488+
try workspace.edit(dependency: dependency, at: dependency.currentRevision!, packageName: aManifest.name, checkoutBranch: "master")
489+
XCTFail("Unexpected edit success")
490+
} catch WorkspaceOperationError.branchAlreadyExists {}
491+
}
492+
}
493+
444494
func testUneditDependency() throws {
445495
mktmpdir { path in
446496
let manifestGraph = try MockManifestGraph(at: path,
@@ -506,6 +556,7 @@ final class WorkspaceTests: XCTestCase {
506556
static var allTests = [
507557
("testBasics", testBasics),
508558
("testEditDependency", testEditDependency),
559+
("testEditDependencyOnNewBranch", testEditDependencyOnNewBranch),
509560
("testDependencyManifestLoading", testDependencyManifestLoading),
510561
("testPackageGraphLoadingBasics", testPackageGraphLoadingBasics),
511562
("testPackageGraphLoadingWithCloning", testPackageGraphLoadingWithCloning),

0 commit comments

Comments
 (0)