@@ -35,101 +35,14 @@ public class GitRepositoryProvider: RepositoryProvider {
35
35
/// Reference to process set, if installed.
36
36
private let processSet : ProcessSet ?
37
37
38
- /// The path to the directory where all cached git repositories are stored.
39
- private let cachePath : AbsolutePath ?
40
-
41
- /// The maximum size of the cache in bytes.
42
- private let maxCacheSize : UInt64
43
-
44
- /// The default location of the git repository cache
45
- private static let defaultCachePath : AbsolutePath ? = {
46
- guard let cacheURL = FileManager . default. urls ( for: . cachesDirectory, in: . userDomainMask) . first else { return nil }
47
- return AbsolutePath ( cacheURL. path) . appending ( components: " org.swift.swiftpm " , " repositories " )
48
- } ( )
49
-
50
38
/// Initializes a GitRepositoryProvider
51
39
/// - Parameters:
52
40
/// - processSet: Reference to process set.
53
41
/// - cachePath: Path to the directory where all cached git repositories are stored. If `nil` is passed as the`cachePath`
54
42
/// fetched repositores will not be cached.
55
43
/// - maxCacheSize: Maximum size of the cache in bytes.
56
- public init ( processSet: ProcessSet ? = nil ,
57
- cachePath: AbsolutePath ? ,
58
- maxCacheSize: UInt64 ? = nil ) {
44
+ public init ( processSet: ProcessSet ? = nil ) {
59
45
self . processSet = processSet
60
- self . cachePath = cachePath
61
- self . maxCacheSize = maxCacheSize ?? 20 * 1024 * 1024 * 1024
62
- }
63
-
64
- /// Initializes a GitRepositoryProvider
65
- /// - Parameters:
66
- /// - processSet: Reference to process set.
67
- /// - maxCacheSize: Maximum size of the cache in bytes.
68
- public convenience init ( processSet: ProcessSet ? = nil ,
69
- maxCacheSize: UInt64 ? = nil ) {
70
- self . init ( processSet: processSet, cachePath: GitRepositoryProvider . defaultCachePath, maxCacheSize: maxCacheSize)
71
- }
72
-
73
- /// Clones the git repository we want to cache into the cache directory if it does not already exist and returns it.
74
- /// If the repository is already cached we perfrom a fetch. In case the `RepositoryProvider`has no `cachePath` or an error occured while
75
- /// setting up the cache `nil` is returned.
76
- private func setupCacheIfNeeded( for repository: RepositorySpecifier ) throws -> GitRepository ? {
77
- guard let cachePath = cachePath else { return nil }
78
- let repositoryPath = cachePath. appending ( component: repository. fileSystemIdentifier)
79
-
80
- do {
81
- let process : Process
82
-
83
- if localFileSystem. exists ( repositoryPath) {
84
- process = Process ( args: Git . tool, " -C " , repositoryPath. pathString, " fetch " )
85
- } else {
86
- try localFileSystem. createDirectory ( repositoryPath, recursive: true )
87
- // We are cloning each repository into its own directory instead of using one large bare repository and
88
- // adding a remote for each repository. This avoids the large overhead that occurs when git tries to
89
- // determine if it has any revision in common with the remote repository, which involves sending a list
90
- // of all local commits to the server (a potentially huge list depending on cache size
91
- // with most commits unrelated to the repository we actually want to fetch).
92
- process = Process ( args: Git . tool, " clone " , " --mirror " , repository. url, repositoryPath. pathString)
93
- }
94
-
95
- try processSet? . add ( process)
96
- let lock = FileLock ( name: repository. fileSystemIdentifier, cachePath: cachePath)
97
- try lock. withLock {
98
- try process. checkNonZeroExit ( )
99
- }
100
- } catch {
101
- return nil
102
- }
103
-
104
- return GitRepository ( path: repositoryPath, isWorkingRepo: false )
105
- }
106
-
107
- /// Purges git repositories from the cache directory in order to free some space.
108
- private func purgeCacheIfNeeded( ) {
109
- guard let cachePath = cachePath else { return }
110
- do {
111
- let cacheSize = try localFileSystem. getDirectorySize ( cachePath)
112
- let desiredCacheSize = maxCacheSize - ( maxCacheSize / 8 )
113
-
114
- guard cacheSize > maxCacheSize else { return }
115
-
116
- let repositories = try localFileSystem. getDirectoryContents ( cachePath)
117
- . map { GitRepository ( path: cachePath. appending ( component: $0) , isWorkingRepo: false ) }
118
- . sorted { try localFileSystem. getFileInfo ( $0. path) . modTime < localFileSystem. getFileInfo ( $1. path) . modTime }
119
-
120
- // Purges repositories until the desired cache size is reached.
121
- for repository in repositories {
122
- let cacheSize = try localFileSystem. getDirectorySize ( cachePath)
123
- guard cacheSize > desiredCacheSize else { break }
124
- let lock = FileLock ( name: repository. path. basename, cachePath: cachePath)
125
- try lock. withLock {
126
- try localFileSystem. removeFileTree ( repository. path)
127
- }
128
- }
129
- } catch {
130
- // The cache seems to be broken. Lets remove everything.
131
- print ( " Error purging cache " )
132
- }
133
46
}
134
47
135
48
public func fetch( repository: RepositorySpecifier , to path: AbsolutePath ) throws {
@@ -138,34 +51,23 @@ public class GitRepositoryProvider: RepositoryProvider {
138
51
// NOTE: We intentionally do not create a shallow clone here; the
139
52
// expected cost of iterative updates on a full clone is less than on a
140
53
// shallow clone.
141
- defer { purgeCacheIfNeeded ( ) }
142
-
143
54
precondition ( !localFileSystem. exists ( path) )
144
55
145
56
// FIXME: We need infrastructure in this subsystem for reporting
146
57
// status information.
147
58
148
- if let cachePath = cachePath, let cache = try setupCacheIfNeeded ( for: repository) {
149
- // Clone the repository using the cache as a reference if possible.
150
- // Git objects are not shared (--dissociate) to avoid problems that might occur when the cache is
151
- // deleted or the package is copied somewhere it cannot reach the cache directory.
152
- let process = Process ( args: Git . tool, " clone " , " --mirror " ,
153
- cache. path. pathString, path. pathString, environment: Git . environment)
154
- try processSet? . add ( process)
155
- let lock = FileLock ( name: cache. path. basename, cachePath: cachePath)
156
- try lock. withLock {
157
- try process. checkGitError ( repository: repository)
158
- }
59
+ let process = Process ( args: Git . tool, " clone " , " --mirror " ,
60
+ repository. url, path. pathString, environment: Git . environment)
61
+ try processSet? . add ( process)
159
62
160
- let clone = GitRepository ( path: path, isWorkingRepo: false )
161
- // In destination repo remove the remote which will be pointing to the cached source repo.
162
- // Set the original remote to the new clone.
163
- try clone. setURL ( remote: " origin " , url: repository. url)
164
- } else {
165
- let process = Process ( args: Git . tool, " clone " , " --mirror " ,
166
- repository. url, path. pathString, environment: Git . environment)
167
- try processSet? . add ( process)
168
- try process. checkGitError ( repository: repository)
63
+ try process. launch ( )
64
+ let result = try process. waitUntilExit ( )
65
+ // Throw if cloning failed.
66
+ guard result. exitStatus == . terminated( code: 0 ) else {
67
+ throw GitCloneError (
68
+ repository: repository. url,
69
+ result: result
70
+ )
169
71
}
170
72
}
171
73
@@ -179,33 +81,20 @@ public class GitRepositoryProvider: RepositoryProvider {
179
81
to destinationPath: AbsolutePath ,
180
82
editable: Bool
181
83
) throws {
182
-
183
- if editable {
184
- // For editable clones, i.e. the user is expected to directly work on them, first we create
185
- // a clone from our cache of repositories and then we replace the remote to the one originally
186
- // present in the bare repository.
187
- try Process . checkNonZeroExit ( args:
188
- Git . tool, " clone " , sourcePath. pathString, destinationPath. pathString)
189
- // The default name of the remote.
190
- let origin = " origin "
191
- // In destination repo remove the remote which will be pointing to the source repo.
192
- let clone = GitRepository ( path: destinationPath)
193
- // Set the original remote to the new clone.
194
- try clone. setURL ( remote: origin, url: repository. url)
195
- // FIXME: This is unfortunate that we have to fetch to update remote's data.
196
- try clone. fetch ( )
197
- } else {
198
- // Clone using a shared object store with the canonical copy.
199
- //
200
- // We currently expect using shared storage here to be safe because we
201
- // only ever expect to attempt to use the working copy to materialize a
202
- // revision we selected in response to dependency resolution, and if we
203
- // re-resolve such that the objects in this repository changed, we would
204
- // only ever expect to get back a revision that remains present in the
205
- // object storage.
206
- try Process . checkNonZeroExit ( args:
207
- Git . tool, " clone " , " --shared " , sourcePath. pathString, destinationPath. pathString)
208
- }
84
+ // For editable clones, i.e. the user is expected to directly work on them, first we create
85
+ // a clone from our cache of repositories and then we replace the remote to the one originally
86
+ // present in the bare repository.
87
+ try Process . checkNonZeroExit ( args: Git . tool, " clone " , sourcePath. pathString, destinationPath. pathString)
88
+ // The default name of the remote.
89
+ let origin = " origin "
90
+ // In destination repo remove the remote which will be pointing to the source repo.
91
+ let clone = GitRepository ( path: destinationPath)
92
+ // Set the original remote to the new clone.
93
+ try clone. setURL ( remote: origin, url: repository. url)
94
+ // FIXME: This is unfortunate that we have to fetch to update remote's data.
95
+ try clone. fetch ( )
96
+ // try Process.checkNonZeroExit(args: Git.tool, "clone", "--reference", sourcePath.pathString,
97
+ // repository.url, "--dissociate", destinationPath.pathString)
209
98
}
210
99
211
100
public func checkoutExists( at path: AbsolutePath ) throws -> Bool {
@@ -879,50 +768,3 @@ private class GitFileSystemView: FileSystem {
879
768
fatalError ( " will never be supported " )
880
769
}
881
770
}
882
-
883
- extension FileSystem {
884
- /// Recursively sums up the file size in bytes of all files inside a directory.
885
- func getDirectorySize( _ path: AbsolutePath ) throws -> UInt64 {
886
- if isFile ( path) {
887
- return try getFileInfo ( path) . size
888
- } else if isDirectory ( path) {
889
- return try getDirectoryContents ( path) . reduce ( 0 ) { $0 + ( try getDirectorySize ( path. appending ( component: $1) ) ) }
890
- } else {
891
- return 0
892
- }
893
- }
894
- }
895
-
896
- extension Process {
897
-
898
- /// Execute a subprocess and get its (UTF-8) output if it has a non zero exit.
899
- /// - Returns: The process output (stdout + stderr).
900
- @discardableResult
901
- public func checkNonZeroExit( ) throws -> String {
902
- try launch ( )
903
- let result = try waitUntilExit ( )
904
- // Throw if there was a non zero termination.
905
- guard result. exitStatus == . terminated( code: 0 ) else {
906
- throw ProcessResult . Error. nonZeroExit ( result)
907
- }
908
- return try result. utf8Output ( )
909
- }
910
-
911
- /// Execute a git subprocess and get its (UTF-8) output if it has a non zero exit.
912
- /// - Parameter repository: The repository the process operates on
913
- /// - Throws: `GitCloneErrorGitCloneError`
914
- /// - Returns: The process output (stdout + stderr).The process output (stdout + stderr).
915
- @discardableResult
916
- public func checkGitError( repository: RepositorySpecifier ) throws -> String {
917
- try launch ( )
918
- let result = try waitUntilExit ( )
919
- // Throw if cloning failed.
920
- guard result. exitStatus == . terminated( code: 0 ) else {
921
- throw GitCloneError (
922
- repository: repository. url,
923
- result: result
924
- )
925
- }
926
- return try result. utf8Output ( )
927
- }
928
- }
0 commit comments