git_cache 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/git_cache/error.rb +4 -4
- data/lib/git_cache/version.rb +1 -1
- data/lib/git_cache.rb +80 -11
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c5d6cf239e6dd1cd7ba89cc4721ddc05dc5a1c1ff2d0c6b7fded9b06bac5eae
|
|
4
|
+
data.tar.gz: b435524513c83ce7fdca4aed22351cb29fda9da8435f6a7af94c591b87791f5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d84f21b22047b18c1d1734d31b84454555d3a5abc8ae7d63ea8b6a9a443393fc0c8cd2bfaf2c8935cd514e3771d999b873ac360a21a107501a7515d44dc7935
|
|
7
|
+
data.tar.gz: 135e7c7031ffa4bf11b2d3b3ace3179c0b4bdaa4b3d67d1e5e1d43b47add706194db510b0bb504a501f873b200f31128d8a4c591f69c9f00f26dd7514f63790d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### v0.1.2 / 2026-08-20
|
|
4
|
+
|
|
5
|
+
* FIXED: Prevent git auto maintenance from racing with cache removals
|
|
6
|
+
|
|
7
|
+
### v0.1.1 / 2026-05-05
|
|
8
|
+
|
|
9
|
+
* BREAKING CHANGE: Make GitCache.sources_writable? private for now
|
|
10
|
+
|
|
3
11
|
### v0.1.0 / 2026-05-04
|
|
4
12
|
|
|
5
13
|
* ADDED: Initial extraction from toys-core
|
data/lib/git_cache/error.rb
CHANGED
|
@@ -9,11 +9,11 @@ class GitCache
|
|
|
9
9
|
# Create a GitCache::Error.
|
|
10
10
|
#
|
|
11
11
|
# @param message [String] The error message
|
|
12
|
-
# @param result [::ExecService::Result] The result of a git
|
|
13
|
-
# command execution
|
|
14
|
-
# command error.
|
|
12
|
+
# @param result [::ExecService::Result,nil] The result of a git
|
|
13
|
+
# command execution. Omit or pass `nil` if this error was not due to
|
|
14
|
+
# a git command error.
|
|
15
15
|
#
|
|
16
|
-
def initialize(message, result)
|
|
16
|
+
def initialize(message, result = nil)
|
|
17
17
|
super(message)
|
|
18
18
|
@exec_result = result
|
|
19
19
|
end
|
data/lib/git_cache/version.rb
CHANGED
data/lib/git_cache.rb
CHANGED
|
@@ -21,6 +21,7 @@ class GitCache
|
|
|
21
21
|
require "digest"
|
|
22
22
|
require "fileutils"
|
|
23
23
|
require "json"
|
|
24
|
+
require "securerandom"
|
|
24
25
|
require "exec_service"
|
|
25
26
|
@cache_dir = ::File.expand_path(cache_dir || default_cache_dir)
|
|
26
27
|
@exec = ::ExecService.new(out: :capture, err: :capture)
|
|
@@ -140,8 +141,7 @@ class GitCache
|
|
|
140
141
|
Array(remotes).map do |remote|
|
|
141
142
|
dir = repo_base_dir_for(remote)
|
|
142
143
|
if ::File.directory?(dir)
|
|
143
|
-
|
|
144
|
-
::FileUtils.rm_rf(dir)
|
|
144
|
+
remove_dir(dir)
|
|
145
145
|
remote
|
|
146
146
|
end
|
|
147
147
|
end.compact.sort
|
|
@@ -203,9 +203,7 @@ class GitCache
|
|
|
203
203
|
end
|
|
204
204
|
results.map(&:sha).uniq.each do |sha|
|
|
205
205
|
unless repo_lock.source_exists?(sha)
|
|
206
|
-
|
|
207
|
-
::FileUtils.chmod_R("u+w", sha_dir, force: true)
|
|
208
|
-
::FileUtils.rm_rf(sha_dir)
|
|
206
|
+
remove_dir(::File.join(dir, sha))
|
|
209
207
|
end
|
|
210
208
|
end
|
|
211
209
|
end
|
|
@@ -217,7 +215,19 @@ class GitCache
|
|
|
217
215
|
FORMAT_VERSION = "v1"
|
|
218
216
|
REPO_DIR_NAME = "repo"
|
|
219
217
|
LOCK_FILE_NAME = "repo.lock"
|
|
220
|
-
|
|
218
|
+
TRASH_DIR_PREFIX = ".trash-"
|
|
219
|
+
|
|
220
|
+
# Config applied to every git invocation. Auto maintenance would otherwise
|
|
221
|
+
# spawn a detached `git maintenance run --auto` process after each fetch,
|
|
222
|
+
# which keeps writing into the cache repo after the fetch has returned, and
|
|
223
|
+
# thus races with our own traversals and removals of that directory. These
|
|
224
|
+
# are managed cache repos that we recreate at will, so background repacking
|
|
225
|
+
# buys them nothing. Requires git 2.30 or later; older gits ignore config
|
|
226
|
+
# keys they do not recognize.
|
|
227
|
+
GIT_CONFIG_ARGS = ["-c", "maintenance.auto=false"].freeze
|
|
228
|
+
|
|
229
|
+
private_constant :REPO_DIR_NAME, :LOCK_FILE_NAME, :FORMAT_VERSION,
|
|
230
|
+
:TRASH_DIR_PREFIX, :GIT_CONFIG_ARGS
|
|
221
231
|
|
|
222
232
|
def repo_base_dir_for(remote)
|
|
223
233
|
::File.join(@cache_dir, ::GitCache.remote_dir_name(remote))
|
|
@@ -229,13 +239,72 @@ class GitCache
|
|
|
229
239
|
end
|
|
230
240
|
|
|
231
241
|
def git(dir, cmd, error_message: nil)
|
|
232
|
-
result = @exec.exec(["git"] + cmd, chdir: dir)
|
|
242
|
+
result = @exec.exec(["git"] + GIT_CONFIG_ARGS + cmd, chdir: dir)
|
|
233
243
|
if !result.success? && error_message
|
|
234
244
|
raise ::GitCache::Error.new(error_message, result)
|
|
235
245
|
end
|
|
236
246
|
result
|
|
237
247
|
end
|
|
238
248
|
|
|
249
|
+
# Removes a directory from the cache.
|
|
250
|
+
#
|
|
251
|
+
# The directory is first renamed out of the way, which is atomic and thus
|
|
252
|
+
# unaffected by any concurrent writes happening inside it, so the cache
|
|
253
|
+
# entry is gone as far as any client is concerned as soon as this returns.
|
|
254
|
+
# Only then is the renamed copy deleted, best effort. Anything left behind
|
|
255
|
+
# is invisible to clients and is swept up by later removals.
|
|
256
|
+
#
|
|
257
|
+
# Raises {GitCache::Error} if the directory could not be removed at all.
|
|
258
|
+
#
|
|
259
|
+
def remove_dir(dir)
|
|
260
|
+
return unless ::File.exist?(dir)
|
|
261
|
+
parent = ::File.dirname(dir)
|
|
262
|
+
begin
|
|
263
|
+
::File.rename(dir, ::File.join(parent, "#{TRASH_DIR_PREFIX}#{::SecureRandom.hex(8)}"))
|
|
264
|
+
rescue ::SystemCallError
|
|
265
|
+
# Some filesystems (notably on Windows) refuse to rename a directory
|
|
266
|
+
# that has open handles under it. Fall back to deleting in place.
|
|
267
|
+
unless remove_dir_in_place(dir)
|
|
268
|
+
raise ::GitCache::Error, "Unable to remove directory: #{dir}"
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
sweep_trash(parent)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Deletes a directory in place, retrying because a concurrent writer can
|
|
275
|
+
# defeat a single pass. Returns whether the directory is gone.
|
|
276
|
+
#
|
|
277
|
+
def remove_dir_in_place(dir, attempts: 3)
|
|
278
|
+
attempts.times do
|
|
279
|
+
chmod_recursive("u+w", dir)
|
|
280
|
+
::FileUtils.rm_rf(dir)
|
|
281
|
+
return true unless ::File.exist?(dir)
|
|
282
|
+
end
|
|
283
|
+
false
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Recursive chmod that tolerates entries disappearing while it runs. The
|
|
287
|
+
# force: option of FileUtils.chmod_R covers only the chmod of each entry,
|
|
288
|
+
# not the traversal that finds them, so a concurrent writer removing a
|
|
289
|
+
# directory mid-walk raises out of chmod_R despite it.
|
|
290
|
+
#
|
|
291
|
+
def chmod_recursive(mode, dir)
|
|
292
|
+
::FileUtils.chmod_R(mode, dir, force: true)
|
|
293
|
+
rescue ::SystemCallError
|
|
294
|
+
nil
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Deletes any leftover trash directories in the given directory. Failures
|
|
298
|
+
# are ignored; the leftovers are harmless and we can try again next time.
|
|
299
|
+
#
|
|
300
|
+
def sweep_trash(dir)
|
|
301
|
+
::Dir.children(dir).each do |child|
|
|
302
|
+
remove_dir_in_place(::File.join(dir, child)) if child.start_with?(TRASH_DIR_PREFIX)
|
|
303
|
+
end
|
|
304
|
+
rescue ::SystemCallError
|
|
305
|
+
nil
|
|
306
|
+
end
|
|
307
|
+
|
|
239
308
|
def ensure_repo_base_dir(remote)
|
|
240
309
|
dir = repo_base_dir_for(remote)
|
|
241
310
|
::FileUtils.mkdir_p(dir)
|
|
@@ -265,8 +334,7 @@ class GitCache
|
|
|
265
334
|
::FileUtils.mkdir_p(repo_dir)
|
|
266
335
|
result = git(repo_dir, ["remote", "get-url", "origin"])
|
|
267
336
|
unless result.success? && result.captured_out.strip == remote
|
|
268
|
-
|
|
269
|
-
::FileUtils.rm_rf(repo_dir)
|
|
337
|
+
remove_dir(repo_dir)
|
|
270
338
|
::FileUtils.mkdir_p(repo_dir)
|
|
271
339
|
git(repo_dir, ["init"],
|
|
272
340
|
error_message: "Unable to initialize git repository")
|
|
@@ -304,11 +372,11 @@ class GitCache
|
|
|
304
372
|
if repo_lock.source_exists?(sha, path)
|
|
305
373
|
::GitCache.safe_join(source_path, path)
|
|
306
374
|
else
|
|
307
|
-
|
|
375
|
+
chmod_recursive("u+w", source_path)
|
|
308
376
|
begin
|
|
309
377
|
copy_from_repo(repo_path, source_path, sha, path)
|
|
310
378
|
ensure
|
|
311
|
-
|
|
379
|
+
chmod_recursive("a-w", source_path) unless ::GitCache.sources_writable?
|
|
312
380
|
end
|
|
313
381
|
end
|
|
314
382
|
repo_lock.access_source!(sha, path)
|
|
@@ -367,6 +435,7 @@ class GitCache
|
|
|
367
435
|
|
|
368
436
|
class << self
|
|
369
437
|
##
|
|
438
|
+
# @private
|
|
370
439
|
# Returns whether shared source files are writable by default.
|
|
371
440
|
# Normally, shared sources are made read-only to protect them from being
|
|
372
441
|
# modified accidentally since multiple clients may be accessing them.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: git_cache
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Azuma
|
|
@@ -61,8 +61,8 @@ licenses:
|
|
|
61
61
|
- MIT
|
|
62
62
|
metadata:
|
|
63
63
|
bug_tracker_uri: https://github.com/dazuma/git_cache/issues
|
|
64
|
-
changelog_uri: https://rubydoc.info/gems/git_cache/0.1.
|
|
65
|
-
documentation_uri: https://rubydoc.info/gems/git_cache/0.1.
|
|
64
|
+
changelog_uri: https://rubydoc.info/gems/git_cache/0.1.2/file/CHANGELOG.md
|
|
65
|
+
documentation_uri: https://rubydoc.info/gems/git_cache/0.1.2
|
|
66
66
|
homepage_uri: https://github.com/dazuma/git_cache
|
|
67
67
|
rdoc_options: []
|
|
68
68
|
require_paths:
|
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
79
|
version: '0'
|
|
80
80
|
requirements: []
|
|
81
|
-
rubygems_version: 4.0.
|
|
81
|
+
rubygems_version: 4.0.16
|
|
82
82
|
specification_version: 4
|
|
83
83
|
summary: A local file system cache of data from git repositories.
|
|
84
84
|
test_files: []
|