Skip to content

Commit

Permalink
Use repository cache without update if offline
Browse files Browse the repository at this point in the history
If there's an error populating the cache, check whether we're offline and have an existing valid repository state. If yes, use cached state and emit a warning.

On macOS, we're using network reachability to determine the connection status, on other platforms we rely on crude parsing of the git error message for now.
  • Loading branch information
neonichu committed Oct 20, 2022
1 parent 47ca7da commit 5769690
Showing 1 changed file with 57 additions and 6 deletions.
63 changes: 57 additions & 6 deletions Sources/SourceControl/RepositoryManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,21 @@ public class RepositoryManager: Cancellable {
}
}
} catch {
cacheUsed = false
// Fetch without populating the cache in the case of an error.
observabilityScope.emit(warning: "skipping cache due to an error: \(error)")
// it is possible that we already created the directory from failed attempts, so clear leftover data if present.
try? self.fileSystem.removeFileTree(repositoryPath)
try self.provider.fetch(repository: handle.repository, to: repositoryPath, progressHandler: updateFetchProgress(progress:))
// If we are offline and have a valid cached repository, use the cache anyway.
if isOffline(error) && self.provider.isValidDirectory(cachedRepositoryPath) {
observabilityScope.emit(warning: "no connectivity, using previously cached repository state")
cacheUsed = true
// Copy the repository from the cache into the repository path.
try self.fileSystem.createDirectory(repositoryPath.parentDirectory, recursive: true)
try self.provider.copy(from: cachedRepositoryPath, to: repositoryPath)
} else {
cacheUsed = false
// Fetch without populating the cache in the case of an error.
observabilityScope.emit(warning: "skipping cache due to an error: \(error)")
// it is possible that we already created the directory from failed attempts, so clear leftover data if present.
try? self.fileSystem.removeFileTree(repositoryPath)
try self.provider.fetch(repository: handle.repository, to: repositoryPath, progressHandler: updateFetchProgress(progress:))
}
}
} else {
// it is possible that we already created the directory from failed attempts, so clear leftover data if present.
Expand Down Expand Up @@ -513,3 +522,45 @@ extension RepositorySpecifier {
}
}

#if canImport(SystemConfiguration)
import SystemConfiguration

private struct Reachability {
let reachability: SCNetworkReachability

init?() {
var emptyAddress = sockaddr()
emptyAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
emptyAddress.sa_family = sa_family_t(AF_INET)

guard let reachability = withUnsafePointer(to: &emptyAddress, {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}) else {
return nil
}
self.reachability = reachability
}

var connectionRequired: Bool {
var flags = SCNetworkReachabilityFlags()
let hasFlags = withUnsafeMutablePointer(to: &flags) {
SCNetworkReachabilityGetFlags(reachability, UnsafeMutablePointer($0))
}
guard hasFlags else { return false }
guard flags.contains(.reachable) else {
return true
}
return flags.contains(.connectionRequired) || flags.contains(.transientConnection)
}
}

fileprivate func isOffline(_ error: Swift.Error) -> Bool {
return Reachability()?.connectionRequired == true
}
#else
fileprivate func isOffline(_ error: Swift.Error) -> Bool {
// TODO: Find a better way to determine reachability on non-Darwin platforms.
return "\(error)".contains("Could not resolve host")
}
#endif

0 comments on commit 5769690

Please sign in to comment.