Skip to content

Commit 9da2275

Browse files
committed
validate local repositry has the correct remote
motivation: in some edge cases, the local repository may be partially valid (the containing directory is a legit git repo, but the repository diretory is not), or otherwise point to a remote different than the one expected changes: * validate that the local reposiotry remote aligns with the expected one, not just that the directory is a valid git repo * refactor validation flow to be more streamlined
1 parent cad8e61 commit 9da2275

File tree

6 files changed

+49
-12
lines changed

6 files changed

+49
-12
lines changed

Sources/SPMTestSupport/InMemoryGitRepository.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,10 @@ public final class InMemoryGitRepositoryProvider: RepositoryProvider {
486486
return true
487487
}
488488

489+
public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool {
490+
return true
491+
}
492+
489493
public func cancel(deadline: DispatchTime) throws {
490494
// noop
491495
}

Sources/SourceControl/GitRepository.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ public struct GitRepositoryProvider: RepositoryProvider, Cancellable {
210210
return result == ".git" || result == "." || result == directory.pathString
211211
}
212212

213+
public func isValidDirectory(_ directory: Basics.AbsolutePath, for repository: RepositorySpecifier) throws -> Bool {
214+
let remoteURL = try self.git.run(["-C", directory.pathString, "remote", "get-url", "origin"])
215+
return remoteURL == repository.url
216+
}
217+
213218
public func copy(from sourcePath: Basics.AbsolutePath, to destinationPath: Basics.AbsolutePath) throws {
214219
try localFileSystem.copy(from: sourcePath, to: destinationPath)
215220
}
@@ -423,10 +428,10 @@ public final class GitRepository: Repository, WorkingCheckout {
423428
self.git = git
424429
self.path = path
425430
self.isWorkingRepo = isWorkingRepo
426-
assert({
431+
/*assert({
427432
// Ignore if we couldn't run popen for some reason.
428433
(try? self.isBare() != isWorkingRepo) ?? true
429-
}())
434+
}())*/
430435
}
431436

432437
/// Private function to invoke the Git tool with its default environment and given set of arguments, specifying the

Sources/SourceControl/Repository.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ public protocol RepositoryProvider: Cancellable {
144144

145145
/// Returns true if the directory is valid git location.
146146
func isValidDirectory(_ directory: AbsolutePath) throws -> Bool
147+
148+
/// Returns true if the directory is valid git location.
149+
func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool
147150
}
148151

149152
/// Abstract repository operations.

Sources/SourceControl/RepositoryManager.swift

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,37 +188,42 @@ public class RepositoryManager: Cancellable {
188188
// sync func and roll the logic into the async version above
189189
private func lookup(
190190
package: PackageIdentity,
191-
repository: RepositorySpecifier,
191+
repository repositorySpecifier: RepositorySpecifier,
192192
updateStrategy: RepositoryUpdateStrategy,
193193
observabilityScope: ObservabilityScope,
194194
delegateQueue: DispatchQueue
195195
) throws -> RepositoryHandle {
196-
let relativePath = try repository.storagePath()
196+
let relativePath = try repositorySpecifier.storagePath()
197197
let repositoryPath = self.path.appending(relativePath)
198-
let handle = RepositoryHandle(manager: self, repository: repository, subpath: relativePath)
198+
let handle = RepositoryHandle(manager: self, repository: repositorySpecifier, subpath: relativePath)
199199

200200
// check if a repository already exists
201201
// errors when trying to check if a repository already exists are legitimate
202202
// and recoverable, and as such can be ignored
203-
if (try? self.provider.repositoryExists(at: repositoryPath)) ?? false {
203+
quick: if (try? self.provider.repositoryExists(at: repositoryPath)) ?? false {
204204
let repository = try handle.open()
205+
206+
guard ((try? self.provider.isValidDirectory(repositoryPath, for: repositorySpecifier)) ?? false) else {
207+
observabilityScope.emit(warning: "\(repositoryPath) is not valid git repository for '\(repositorySpecifier.location)', will fetch again.")
208+
break quick
209+
}
210+
205211
// Update the repository if needed
206212
if self.fetchRequired(repository: repository, updateStrategy: updateStrategy) {
207213
let start = DispatchTime.now()
208-
214+
209215
delegateQueue.async {
210216
self.delegate?.willUpdate(package: package, repository: handle.repository)
211217
}
212-
218+
213219
try repository.fetch()
214220
let duration = start.distance(to: .now())
215221
delegateQueue.async {
216222
self.delegate?.didUpdate(package: package, repository: handle.repository, duration: duration)
217223
}
218-
return handle
219-
} else if try self.provider.isValidDirectory(repositoryPath) {
220-
return handle
221224
}
225+
226+
return handle
222227
}
223228

224229
// inform delegate that we are starting to fetch
@@ -334,7 +339,7 @@ public class RepositoryManager: Cancellable {
334339
}
335340
} catch {
336341
// If we are offline and have a valid cached repository, use the cache anyway.
337-
if try isOffline(error) && self.provider.isValidDirectory(cachedRepositoryPath) {
342+
if try isOffline(error) && self.provider.isValidDirectory(cachedRepositoryPath, for: handle.repository) {
338343
// For the first offline use in the lifetime of this repository manager, emit a warning.
339344
if self.emitNoConnectivityWarning.get(default: false) {
340345
self.emitNoConnectivityWarning.put(false)
@@ -426,6 +431,10 @@ public class RepositoryManager: Cancellable {
426431
try self.provider.isValidDirectory(directory)
427432
}
428433

434+
public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool {
435+
try self.provider.isValidDirectory(directory, for: repository)
436+
}
437+
429438
/// Reset the repository manager.
430439
///
431440
/// Note: This also removes the cloned repositories from the disk.

Tests/SourceControlTests/RepositoryManagerTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,10 @@ class RepositoryManagerTests: XCTestCase {
561561
fatalError("should not be called")
562562
}
563563

564+
public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool {
565+
fatalError("should not be called")
566+
}
567+
564568
func cancel(deadline: DispatchTime) throws {
565569
print("cancel")
566570
}
@@ -640,6 +644,10 @@ private class DummyRepository: Repository {
640644
fatalError("unexpected API call")
641645
}
642646

647+
public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool {
648+
fatalError("unexpected API call")
649+
}
650+
643651
func fetch() throws {
644652
self.provider.increaseFetchCount()
645653
}
@@ -720,6 +728,10 @@ private class DummyRepositoryProvider: RepositoryProvider {
720728
return true
721729
}
722730

731+
func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool {
732+
return true
733+
}
734+
723735
func cancel(deadline: DispatchTime) throws {
724736
// noop
725737
}

Tests/WorkspaceTests/SourceControlPackageContainerTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ private class MockRepositories: RepositoryProvider {
150150
return true
151151
}
152152

153+
public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool {
154+
return true
155+
}
156+
153157
func cancel(deadline: DispatchTime) throws {
154158
// noop
155159
}

0 commit comments

Comments
 (0)