Skip to content

Commit 87a86dc

Browse files
authored
use shallow clones for registry/packages (when supported by LibGit2) (#4487)
1 parent efe1eaf commit 87a86dc

File tree

5 files changed

+86
-17
lines changed

5 files changed

+86
-17
lines changed

src/API.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,11 +1346,11 @@ function instantiate(
13461346
end
13471347
repo_path = Types.add_repo_cache_path(repo_source)
13481348
let repo_source = repo_source
1349-
LibGit2.with(GitTools.ensure_clone(ctx.io, repo_path, repo_source; isbare = true)) do repo
1349+
LibGit2.with(GitTools.ensure_clone(ctx.io, repo_path, repo_source; isbare = true, depth = 1)) do repo
13501350
# We only update the clone if the tree hash can't be found
13511351
tree_hash_object = tree_hash(repo, string(pkg.tree_hash))
13521352
if tree_hash_object === nothing
1353-
GitTools.fetch(ctx.io, repo, repo_source; refspecs = Types.refspecs)
1353+
GitTools.fetch(ctx.io, repo, repo_source; refspecs = Types.refspecs, depth = LibGit2.Consts.FETCH_DEPTH_UNSHALLOW)
13541354
tree_hash_object = tree_hash(repo, string(pkg.tree_hash))
13551355
end
13561356
if tree_hash_object === nothing

src/GitTools.jl

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,45 @@ using Printf
1313
use_cli_git() = Base.get_bool_env("JULIA_PKG_USE_CLI_GIT", false)
1414
const RESOLVING_DELTAS_HEADER = "Resolving Deltas:"
1515

16+
# Check if LibGit2 supports shallow clones (requires LibGit2 >= 1.7.0)
17+
# We check both the LibGit2 version and the existence of `isshallow` to ensure
18+
# the shallow clone functionality is available
19+
function supports_shallow_clone()
20+
# This seems buggy on Windows? Get some weird CI errors with it.
21+
if Sys.iswindows()
22+
return false
23+
end
24+
has_version = @static if isdefined(LibGit2, :VERSION)
25+
LibGit2.VERSION >= v"1.7.0"
26+
else
27+
false
28+
end
29+
has_isshallow = isdefined(LibGit2, :isshallow)
30+
return has_version && has_isshallow
31+
end
32+
33+
# Check if a URL is a local path or file:// URL
34+
# Shallow clones are only supported for network protocols (HTTP, HTTPS, Git, SSH)
35+
function is_local_repo(url::AbstractString)
36+
# Check if it's a local filesystem path
37+
ispath(url) && return true
38+
# Check if it uses file:// protocol
39+
startswith(url, "file://") && return true
40+
return false
41+
end
42+
43+
# Check if a repository is a shallow clone
44+
function isshallow(repo::LibGit2.GitRepo)
45+
if supports_shallow_clone() && isdefined(LibGit2, :isshallow)
46+
return LibGit2.isshallow(repo)
47+
else
48+
# Fallback: check for .git/shallow file
49+
repo_path = LibGit2.path(repo)
50+
shallow_file = joinpath(repo_path, "shallow")
51+
return isfile(shallow_file)
52+
end
53+
end
54+
1655
function transfer_progress(progress::Ptr{LibGit2.TransferProgress}, p::Any)
1756
progress = unsafe_load(progress)
1857
@assert haskey(p, :transfer_progress)
@@ -89,11 +128,17 @@ function checkout_tree_to_path(repo::LibGit2.GitRepo, tree::LibGit2.GitObject, p
89128
end
90129
end
91130

92-
function clone(io::IO, url, source_path; header = nothing, credentials = nothing, isbare = false, kwargs...)
131+
function clone(io::IO, url, source_path; header = nothing, credentials = nothing, isbare = false, depth::Integer = 0, kwargs...)
93132
url = String(url)::String
94133
source_path = String(source_path)::String
95134
@assert !isdir(source_path) || isempty(readdir(source_path))
96135
url = normalize_url(url)
136+
137+
# Disable shallow clones for local repos (not supported) or if LibGit2 doesn't support it
138+
if depth > 0 && (is_local_repo(url) || !supports_shallow_clone())
139+
depth = 0
140+
end
141+
97142
printpkgstyle(io, :Cloning, header === nothing ? "git-repo `$url`" : header)
98143
bar = MiniProgressBar(header = "Cloning:", color = Base.info_color())
99144
fancyprint = can_fancyprint(io)
@@ -103,8 +148,10 @@ function clone(io::IO, url, source_path; header = nothing, credentials = nothing
103148
end
104149
return try
105150
if use_cli_git()
106-
args = ["--quiet", url, source_path]
107-
isbare && pushfirst!(args, "--bare")
151+
args = ["--quiet"]
152+
depth > 0 && push!(args, "--depth=$depth")
153+
isbare && push!(args, "--bare")
154+
push!(args, url, source_path)
108155
cmd = `git clone $args`
109156
try
110157
run(pipeline(cmd; stdout = devnull))
@@ -124,7 +171,12 @@ function clone(io::IO, url, source_path; header = nothing, credentials = nothing
124171
LibGit2.Callbacks()
125172
end
126173
mkpath(source_path)
127-
return LibGit2.clone(url, source_path; callbacks, credentials, isbare, kwargs...)
174+
# Only pass depth if shallow clones are supported and depth > 0
175+
if depth > 0
176+
return LibGit2.clone(url, source_path; callbacks, credentials, isbare, depth, kwargs...)
177+
else
178+
return LibGit2.clone(url, source_path; callbacks, credentials, isbare, kwargs...)
179+
end
128180
end
129181
catch err
130182
rm(source_path; force = true, recursive = true)
@@ -149,10 +201,16 @@ function geturl(repo)
149201
end
150202
end
151203

152-
function fetch(io::IO, repo::LibGit2.GitRepo, remoteurl = nothing; header = nothing, credentials = nothing, refspecs = [""], kwargs...)
204+
function fetch(io::IO, repo::LibGit2.GitRepo, remoteurl = nothing; header = nothing, credentials = nothing, refspecs = [""], depth::Integer = 0, kwargs...)
153205
if remoteurl === nothing
154206
remoteurl = geturl(repo)
155207
end
208+
209+
# Disable shallow fetches for local repos (not supported) or if LibGit2 doesn't support it
210+
if depth > 0 && (is_local_repo(remoteurl) || !supports_shallow_clone())
211+
depth = 0
212+
end
213+
156214
fancyprint = can_fancyprint(io)
157215
remoteurl = normalize_url(remoteurl)
158216
printpkgstyle(io, :Updating, header === nothing ? "git-repo `$remoteurl`" : header)
@@ -174,15 +232,23 @@ function fetch(io::IO, repo::LibGit2.GitRepo, remoteurl = nothing; header = noth
174232
return try
175233
if use_cli_git()
176234
let remoteurl = remoteurl
177-
cmd = `git -C $(LibGit2.path(repo)) fetch -q $remoteurl $(only(refspecs))`
235+
args = ["-C", LibGit2.path(repo), "fetch", "-q"]
236+
depth > 0 && push!(args, "--depth=$depth")
237+
push!(args, remoteurl, only(refspecs))
238+
cmd = `git $args`
178239
try
179240
run(pipeline(cmd; stdout = devnull))
180241
catch err
181242
Pkg.Types.pkgerror("The command $(cmd) failed, error: $err")
182243
end
183244
end
184245
else
185-
return LibGit2.fetch(repo; remoteurl, callbacks, credentials, refspecs, kwargs...)
246+
# Only pass depth if shallow clones are supported and depth > 0
247+
if depth > 0
248+
return LibGit2.fetch(repo; remoteurl, callbacks, credentials, refspecs, depth, kwargs...)
249+
else
250+
return LibGit2.fetch(repo; remoteurl, callbacks, credentials, refspecs, kwargs...)
251+
end
186252
end
187253
catch err
188254
err isa LibGit2.GitError || rethrow()

src/Operations.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ function install_git(
11541154
first_url = first(urls)
11551155
repo = GitTools.ensure_clone(
11561156
io, repo_path, first_url; isbare = true,
1157-
header = "[$uuid] $name from $first_url"
1157+
header = "[$uuid] $name from $first_url", depth = 1
11581158
)
11591159
git_hash = LibGit2.GitHash(hash.bytes)
11601160
for url in urls
@@ -1165,7 +1165,7 @@ function install_git(
11651165
catch err
11661166
err isa LibGit2.GitError && err.code == LibGit2.Error.ENOTFOUND || rethrow()
11671167
end
1168-
GitTools.fetch(io, repo, url, refspecs = refspecs)
1168+
GitTools.fetch(io, repo, url, refspecs = refspecs, depth = LibGit2.Consts.FETCH_DEPTH_UNSHALLOW)
11691169
end
11701170
tree = try
11711171
LibGit2.GitObject(repo, git_hash)

src/Registry/Registry.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,8 @@ function download_registries(io::IO, regs::Vector{RegistrySpec}, depots::Union{S
351351
return
352352
elseif reg.url !== nothing # clone from url
353353
# retry to help spurious connection issues, particularly on CI
354-
repo = retry(GitTools.clone, delays = fill(1.0, 5), check = (s, e) -> isa(e, LibGit2.GitError))(io, reg.url, tmp; header = "registry from $(repr(reg.url))")
354+
# Use shallow clone (depth=1) for registries since we only need the latest state
355+
repo = retry(GitTools.clone, delays = fill(1.0, 5), check = (s, e) -> isa(e, LibGit2.GitError))(io, reg.url, tmp; header = "registry from $(repr(reg.url))", depth = 1)
355356
LibGit2.close(repo)
356357
else
357358
Pkg.Types.pkgerror("no path or url specified for registry")
@@ -632,7 +633,9 @@ function update(regs::Vector{RegistrySpec}; io::IO = stderr_f(), force::Bool = t
632633
end
633634
branch = LibGit2.headname(repo)
634635
try
635-
GitTools.fetch(io, repo; refspecs = ["+refs/heads/$branch:refs/remotes/origin/$branch"])
636+
# If this is a shallow clone, continue using shallow fetches
637+
fetch_depth = GitTools.isshallow(repo) ? 1 : 0
638+
GitTools.fetch(io, repo; refspecs = ["+refs/heads/$branch:refs/remotes/origin/$branch"], depth = fetch_depth)
636639
catch e
637640
e isa Pkg.Types.PkgError || rethrow()
638641
push!(errors, (reg.path, "failed to fetch from repo: $(e.msg)"))

src/Types.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ function handle_repo_add!(ctx::Context, pkg::PackageSpec)
10101010

10111011
return let repo_source = repo_source
10121012
# The type-assertions below are necessary presumably due to julia#36454
1013-
LibGit2.with(GitTools.ensure_clone(ctx.io, add_repo_cache_path(repo_source::Union{Nothing, String}), repo_source::Union{Nothing, String}; isbare = true)) do repo
1013+
LibGit2.with(GitTools.ensure_clone(ctx.io, add_repo_cache_path(repo_source::Union{Nothing, String}), repo_source::Union{Nothing, String}; isbare = true, depth = 1)) do repo
10141014
repo_source_typed = repo_source::Union{Nothing, String}
10151015
GitTools.check_valid_HEAD(repo)
10161016
create_cachedir_tag(dirname(add_repo_cache_path(repo_source)))
@@ -1027,11 +1027,11 @@ function handle_repo_add!(ctx::Context, pkg::PackageSpec)
10271027
if startswith(rev_or_hash, "pull/") && endswith(rev_or_hash, "/head")
10281028
pr_number = rev_or_hash[6:(end - 5)] # Extract number from "pull/X/head"
10291029
pr_refspecs = ["+refs/pull/$(pr_number)/head:refs/cache/pull/$(pr_number)/head"]
1030-
GitTools.fetch(ctx.io, repo, repo_source_typed; refspecs = pr_refspecs)
1030+
GitTools.fetch(ctx.io, repo, repo_source_typed; refspecs = pr_refspecs, depth = 1)
10311031
# For branch names, fetch only the specific branch
10321032
elseif !looks_like_commit_hash(string(rev_or_hash))
10331033
specific_refspec = ["+refs/heads/$(rev_or_hash):refs/cache/heads/$(rev_or_hash)"]
1034-
GitTools.fetch(ctx.io, repo, repo_source_typed; refspecs = specific_refspec)
1034+
GitTools.fetch(ctx.io, repo, repo_source_typed; refspecs = specific_refspec, depth = 1)
10351035
else
10361036
# For commit hashes, fetch all branches
10371037
GitTools.fetch(ctx.io, repo, repo_source_typed; refspecs = refspecs)
@@ -1054,7 +1054,7 @@ function handle_repo_add!(ctx::Context, pkg::PackageSpec)
10541054
if isbranch && !fetched && !ispinned
10551055
# Fetch only the specific branch being tracked
10561056
specific_refspec = ["+refs/heads/$(rev_or_hash):refs/cache/heads/$(rev_or_hash)"]
1057-
GitTools.fetch(ctx.io, repo, repo_source_typed; refspecs = specific_refspec)
1057+
GitTools.fetch(ctx.io, repo, repo_source_typed; refspecs = specific_refspec, depth = 1)
10581058
gitobject, isbranch = get_object_or_branch(repo, rev_or_hash)
10591059
end
10601060

0 commit comments

Comments
 (0)