Skip to content

Commit 359f39a

Browse files
omusKristofferC
authored andcommitted
Support separate payloads for RemoteCallbacks (#26437)
* Deprecate LibGit2 keyword: payload LibGit2 functions `clone`, `fetch`, and `push` all used a keyword named `payload` which would only take credential information. The keyword was renamed to `credentials` to be more accurate and the original keyword is now deprecated. * Support separate payloads for RemoteCallbacks Make it easier to support mixing user created callbacks and default callbacks defined by LibGit2. * Callbacks documentation * Add test for credential callback conflict * Add TransferProgress struct * Add online callback test
1 parent 8a5f747 commit 359f39a

File tree

8 files changed

+217
-80
lines changed

8 files changed

+217
-80
lines changed

stdlib/LibGit2/src/LibGit2.jl

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -259,33 +259,47 @@ The keyword arguments are:
259259
* `remoteurl::AbstractString=""`: the URL of `remote`. If not specified,
260260
will be assumed based on the given name of `remote`.
261261
* `refspecs=AbstractString[]`: determines properties of the fetch.
262-
* `payload=CredentialPayload()`: provides credentials and/or settings when authenticating
263-
against a private `remote`.
262+
* `credentials=nothing`: provides credentials and/or settings when authenticating against
263+
a private `remote`.
264+
* `callbacks=Callbacks()`: user provided callbacks and payloads.
264265
265266
Equivalent to `git fetch [<remoteurl>|<repo>] [<refspecs>]`.
266267
"""
267268
function fetch(repo::GitRepo; remote::AbstractString="origin",
268269
remoteurl::AbstractString="",
269270
refspecs::Vector{<:AbstractString}=AbstractString[],
270-
payload::Union{CredentialPayload, AbstractCredential, CachedCredentials, Nothing}=CredentialPayload())
271-
p = reset!(deprecate_nullable_creds(:fetch, "repo", payload), GitConfig(repo))
271+
payload::Creds=nothing,
272+
credentials::Creds=payload,
273+
callbacks::Callbacks=Callbacks())
272274
rmt = if isempty(remoteurl)
273275
get(GitRemote, repo, remote)
274276
else
275277
GitRemoteAnon(repo, remoteurl)
276278
end
279+
280+
deprecate_payload_keyword(:fetch, "repo", payload)
281+
cred_payload = reset!(CredentialPayload(credentials), GitConfig(repo))
282+
if !haskey(callbacks, :credentials)
283+
callbacks[:credentials] = (credentials_cb(), cred_payload)
284+
elseif haskey(callbacks, :credentials) && credentials !== nothing
285+
throw(ArgumentError(string(
286+
"Unable to both use the provided `credentials` as a payload when the ",
287+
"`callbacks` also contain a credentials payload.")))
288+
end
289+
277290
result = try
278-
fo = FetchOptions(callbacks=RemoteCallbacks(credentials=credentials_cb(), payload=p))
279-
fetch(rmt, refspecs, msg="from $(url(rmt))", options = fo)
291+
remote_callbacks = RemoteCallbacks(callbacks)
292+
fo = FetchOptions(callbacks=remote_callbacks)
293+
fetch(rmt, refspecs, msg="from $(url(rmt))", options=fo)
280294
catch err
281295
if isa(err, GitError) && err.code == Error.EAUTH
282-
reject(payload)
296+
reject(cred_payload)
283297
end
284298
rethrow()
285299
finally
286300
close(rmt)
287301
end
288-
approve(payload)
302+
approve(cred_payload)
289303
return result
290304
end
291305

@@ -300,34 +314,48 @@ The keyword arguments are:
300314
* `refspecs=AbstractString[]`: determines properties of the push.
301315
* `force::Bool=false`: determines if the push will be a force push,
302316
overwriting the remote branch.
303-
* `payload=CredentialPayload()`: provides credentials and/or settings when authenticating
304-
against a private `remote`.
317+
* `credentials=nothing`: provides credentials and/or settings when authenticating against
318+
a private `remote`.
319+
* `callbacks=Callbacks()`: user provided callbacks and payloads.
305320
306321
Equivalent to `git push [<remoteurl>|<repo>] [<refspecs>]`.
307322
"""
308323
function push(repo::GitRepo; remote::AbstractString="origin",
309324
remoteurl::AbstractString="",
310325
refspecs::Vector{<:AbstractString}=AbstractString[],
311326
force::Bool=false,
312-
payload::Union{CredentialPayload, AbstractCredential, CachedCredentials, Nothing}=CredentialPayload())
313-
p = reset!(deprecate_nullable_creds(:push, "repo", payload), GitConfig(repo))
327+
payload::Creds=nothing,
328+
credentials::Creds=payload,
329+
callbacks::Callbacks=Callbacks())
314330
rmt = if isempty(remoteurl)
315331
get(GitRemote, repo, remote)
316332
else
317333
GitRemoteAnon(repo, remoteurl)
318334
end
335+
336+
deprecate_payload_keyword(:push, "repo", payload)
337+
cred_payload = reset!(CredentialPayload(credentials), GitConfig(repo))
338+
if !haskey(callbacks, :credentials)
339+
callbacks[:credentials] = (credentials_cb(), cred_payload)
340+
elseif haskey(callbacks, :credentials) && credentials !== nothing
341+
throw(ArgumentError(string(
342+
"Unable to both use the provided `credentials` as a payload when the ",
343+
"`callbacks` also contain a credentials payload.")))
344+
end
345+
319346
result = try
320-
push_opts = PushOptions(callbacks=RemoteCallbacks(credentials=credentials_cb(), payload=p))
347+
remote_callbacks = RemoteCallbacks(callbacks)
348+
push_opts = PushOptions(callbacks=remote_callbacks)
321349
push(rmt, refspecs, force=force, options=push_opts)
322350
catch err
323351
if isa(err, GitError) && err.code == Error.EAUTH
324-
reject(payload)
352+
reject(cred_payload)
325353
end
326354
rethrow()
327355
finally
328356
close(rmt)
329357
end
330-
approve(payload)
358+
approve(cred_payload)
331359
return result
332360
end
333361

@@ -507,8 +535,9 @@ The keyword arguments are:
507535
* `remote_cb::Ptr{Cvoid}=C_NULL`: a callback which will be used to create the remote
508536
before it is cloned. If `C_NULL` (the default), no attempt will be made to create
509537
the remote - it will be assumed to already exist.
510-
* `payload::CredentialPayload=CredentialPayload()`: provides credentials and/or settings
511-
when authenticating against a private repository.
538+
* `credentials::Creds=nothing`: provides credentials and/or settings when authenticating
539+
against a private repository.
540+
* `callbacks::Callbacks=Callbacks()`: user provided callbacks and payloads.
512541
513542
Equivalent to `git clone [-b <branch>] [--bare] <repo_url> <repo_path>`.
514543
@@ -525,12 +554,24 @@ function clone(repo_url::AbstractString, repo_path::AbstractString;
525554
branch::AbstractString="",
526555
isbare::Bool = false,
527556
remote_cb::Ptr{Cvoid} = C_NULL,
528-
payload::Union{CredentialPayload, AbstractCredential, CachedCredentials, Nothing}=CredentialPayload())
557+
payload::Creds=nothing,
558+
credentials::Creds=payload,
559+
callbacks::Callbacks=Callbacks())
560+
deprecate_payload_keyword(:clone, "repo_url, repo_path", payload)
561+
cred_payload = reset!(CredentialPayload(credentials))
562+
if !haskey(callbacks, :credentials)
563+
callbacks[:credentials] = (credentials_cb(), cred_payload)
564+
elseif haskey(callbacks, :credentials) && credentials !== nothing
565+
throw(ArgumentError(string(
566+
"Unable to both use the provided `credentials` as a payload when the ",
567+
"`callbacks` also contain a credentials payload.")))
568+
end
569+
529570
# setup clone options
530571
lbranch = Base.cconvert(Cstring, branch)
531572
GC.@preserve lbranch begin
532-
p = reset!(deprecate_nullable_creds(:clone, "repo_url, repo_path", payload))
533-
fetch_opts = FetchOptions(callbacks = RemoteCallbacks(credentials=credentials_cb(), payload=p))
573+
remote_callbacks = RemoteCallbacks(callbacks)
574+
fetch_opts = FetchOptions(callbacks=remote_callbacks)
534575
clone_opts = CloneOptions(
535576
bare = Cint(isbare),
536577
checkout_branch = isempty(lbranch) ? Cstring(C_NULL) : Base.unsafe_convert(Cstring, lbranch),
@@ -541,12 +582,12 @@ function clone(repo_url::AbstractString, repo_path::AbstractString;
541582
clone(repo_url, repo_path, clone_opts)
542583
catch err
543584
if isa(err, GitError) && err.code == Error.EAUTH
544-
reject(payload)
585+
reject(cred_payload)
545586
end
546587
rethrow()
547588
end
548589
end
549-
approve(payload)
590+
approve(cred_payload)
550591
return repo
551592
end
552593

stdlib/LibGit2/src/callbacks.jl

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,9 @@ For addition details see the LibGit2 guide on
261261
"""
262262
function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring,
263263
username_ptr::Cstring,
264-
allowed_types::Cuint, payload_ptr::Ptr{Cvoid})
264+
allowed_types::Cuint, p::CredentialPayload)
265265
err = Cint(0)
266266

267-
# get `CredentialPayload` object from payload pointer
268-
@assert payload_ptr != C_NULL
269-
p = unsafe_pointer_to_objref(payload_ptr)::CredentialPayload
270-
271267
# Parse URL only during the first call to this function. Future calls will use the
272268
# information cached inside the payload.
273269
if isempty(p.url)
@@ -340,6 +336,13 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring,
340336
return err
341337
end
342338

339+
function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring,
340+
username_ptr::Cstring, allowed_types::Cuint,
341+
payloads::Dict)
342+
p = payloads[:credentials]
343+
credentials_callback(libgit2credptr, url_ptr, username_ptr, allowed_types, p)
344+
end
345+
343346
function fetchhead_foreach_callback(ref_name::Cstring, remote_url::Cstring,
344347
oid_ptr::Ptr{GitHash}, is_merge::Cuint, payload::Ptr{Cvoid})
345348
fhead_vec = unsafe_pointer_to_objref(payload)::Vector{FetchHead}
@@ -351,6 +354,6 @@ end
351354
"C function pointer for `mirror_callback`"
352355
mirror_cb() = cfunction(mirror_callback, Cint, Tuple{Ptr{Ptr{Cvoid}}, Ptr{Cvoid}, Cstring, Cstring, Ptr{Cvoid}})
353356
"C function pointer for `credentials_callback`"
354-
credentials_cb() = cfunction(credentials_callback, Cint, Tuple{Ptr{Ptr{Cvoid}}, Cstring, Cstring, Cuint, Ptr{Cvoid}})
357+
credentials_cb() = cfunction(credentials_callback, Cint, Tuple{Ptr{Ptr{Cvoid}}, Cstring, Cstring, Cuint, Any})
355358
"C function pointer for `fetchhead_foreach_callback`"
356359
fetchhead_foreach_cb() = cfunction(fetchhead_foreach_callback, Cint, Tuple{Cstring, Cstring, Ptr{GitHash}, Cuint, Ptr{Cvoid}})

stdlib/LibGit2/src/deprecated.jl

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,15 @@ function prompt(msg::AbstractString; default::AbstractString="", password::Bool=
2424
coalesce(Base.prompt(msg, default=default, password=password), "")
2525
end
2626

27-
# PR #23640
28-
# when this deprecation is deleted, remove all calls to it, and replace all keywords of:
29-
# `payload::Union{CredentialPayload, AbstractCredential, CachedCredentials, Nothing}`
30-
# with `payload::CredentialPayload` from base/libgit2/libgit2.jl
31-
function deprecate_nullable_creds(f, sig, payload)
32-
if isa(payload, Union{AbstractCredential, CachedCredentials, Nothing})
33-
# Note: Be careful not to show the contents of the credentials as it could reveal a
34-
# password.
35-
if payload === nothing
36-
msg = "`LibGit2.$f($sig; payload=nothing)` is deprecated, use "
37-
msg *= "`LibGit2.$f($sig; payload=LibGit2.CredentialPayload())` instead."
38-
p = CredentialPayload()
39-
else
40-
cred = payload
41-
C = typeof(cred)
42-
msg = "`LibGit2.$f($sig; payload=$C(...))` is deprecated, use "
43-
msg *= "`LibGit2.$f($sig; payload=LibGit2.CredentialPayload($C(...)))` instead."
44-
p = CredentialPayload(cred)
45-
end
46-
Base.depwarn(msg, f)
47-
else
48-
p = payload::CredentialPayload
27+
# PR #26437
28+
# when this deprecation is deleted, remove all calls to it, and remove the keyword of:
29+
# `payload` from "src/LibGit2.jl"
30+
function deprecate_payload_keyword(f, sig, payload)
31+
if payload !== nothing
32+
Base.depwarn(string(
33+
"`LibGit2.$f($sig; payload=cred)` is deprecated, use ",
34+
"`LibGit2.$f($sig; credentials=cred)` instead."), f)
4935
end
50-
return p
5136
end
5237

5338
@deprecate get_creds!(cache::CachedCredentials, credid, default) get!(cache, credid, default)

stdlib/LibGit2/src/types.jl

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,21 @@ The fields represent:
188188
perfdata_payload::Ptr{Cvoid}
189189
end
190190

191-
abstract type Payload end
191+
"""
192+
LibGit2.TransferProgress
193+
194+
Transfer progress information used by the `transfer_progress` remote callback.
195+
Matches the [`git_transfer_progress`](https://libgit2.github.com/libgit2/#HEAD/type/git_transfer_progress) struct.
196+
"""
197+
@kwdef struct TransferProgress
198+
total_objects::Cuint
199+
indexed_objects::Cuint
200+
received_objects::Cuint
201+
local_objects::Cuint
202+
total_deltas::Cuint
203+
indexed_deltas::Cuint
204+
received_bytes::Csize_t
205+
end
192206

193207
@kwdef struct RemoteCallbacksStruct
194208
version::Cuint = 1
@@ -206,6 +220,28 @@ abstract type Payload end
206220
payload::Ptr{Cvoid}
207221
end
208222

223+
"""
224+
LibGit2.Callbacks
225+
226+
A dictionary which containing the callback name as the key and the value as a tuple of the
227+
callback function and payload.
228+
229+
The `Callback` dictionary to construct `RemoteCallbacks` allows each callback to use a
230+
distinct payload. Each callback, when called, will receive `Dict` which will hold the
231+
callback's custom payload which can be accessed using the callback name.
232+
233+
# Examples
234+
```julia
235+
julia> c = LibGit2.Callbacks(:credentials => (LibGit2.credentials_cb(), LibGit2.CredentialPayload()));
236+
237+
julia> LibGit2.clone(url, callbacks=c);
238+
```
239+
240+
See [`git_remote_callbacks`](https://libgit2.github.com/libgit2/#HEAD/type/git_remote_callbacks)
241+
for details on supported callbacks.
242+
"""
243+
const Callbacks = Dict{Symbol, Tuple{Ptr{Cvoid}, Any}}
244+
209245
"""
210246
LibGit2.RemoteCallbacks
211247
@@ -215,17 +251,27 @@ Matches the [`git_remote_callbacks`](https://libgit2.github.com/libgit2/#HEAD/ty
215251
struct RemoteCallbacks
216252
cb::RemoteCallbacksStruct
217253
gcroot::Ref{Any}
218-
function RemoteCallbacks(; payload::Union{Payload, Nothing}=nothing, kwargs...)
254+
255+
function RemoteCallbacks(; version::Cuint=Cuint(1), payload=C_NULL, callbacks...)
219256
p = Ref{Any}(payload)
220-
if payload === nothing
221-
pp = C_NULL
222-
else
223-
pp = unsafe_load(Ptr{Ptr{Cvoid}}(Base.unsafe_convert(Ptr{Any}, p)))
224-
end
225-
return new(RemoteCallbacksStruct(; kwargs..., payload=pp), p)
257+
pp = unsafe_load(Ptr{Ptr{Cvoid}}(Base.unsafe_convert(Ptr{Any}, p)))
258+
return new(RemoteCallbacksStruct(; version=version, payload=pp, callbacks...), p)
226259
end
227260
end
228261

262+
function RemoteCallbacks(c::Callbacks)
263+
callbacks = Dict{Symbol, Ptr{Cvoid}}()
264+
payloads = Dict{Symbol, Any}()
265+
266+
for (name, (callback, payload)) in c
267+
callbacks[name] = callback
268+
payloads[name] = payload
269+
end
270+
271+
RemoteCallbacks(; payload=payloads, callbacks...)
272+
end
273+
274+
229275
"""
230276
LibGit2.ProxyOptions
231277
@@ -1250,7 +1296,7 @@ Retains the state between multiple calls to the credential callback for the same
12501296
A `CredentialPayload` instance is expected to be `reset!` whenever it will be used with a
12511297
different URL.
12521298
"""
1253-
mutable struct CredentialPayload <: Payload
1299+
mutable struct CredentialPayload
12541300
explicit::Union{AbstractCredential, Nothing}
12551301
cache::Union{CachedCredentials, Nothing}
12561302
allow_ssh_agent::Bool # Allow the use of the SSH agent to get credentials
@@ -1293,6 +1339,9 @@ function CredentialPayload(cache::CachedCredentials; kwargs...)
12931339
CredentialPayload(nothing, cache; kwargs...)
12941340
end
12951341

1342+
CredentialPayload(p::CredentialPayload) = p
1343+
1344+
12961345
"""
12971346
reset!(payload, [config]) -> CredentialPayload
12981347
@@ -1364,3 +1413,6 @@ function reject(p::CredentialPayload; shred::Bool=true)
13641413
shred && securezero!(cred)
13651414
nothing
13661415
end
1416+
1417+
# Useful for functions which can handle various kinds of credentials
1418+
const Creds = Union{CredentialPayload, AbstractCredential, CachedCredentials, Nothing}

stdlib/LibGit2/test/libgit2-helpers.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
import LibGit2: AbstractCredential, UserPasswordCredential, SSHCredential,
4-
CachedCredentials, CredentialPayload, Payload
3+
using LibGit2: AbstractCredential, UserPasswordCredential, SSHCredential,
4+
CachedCredentials, CredentialPayload
55
using Base: coalesce
66

77
const DEFAULT_PAYLOAD = CredentialPayload(allow_ssh_agent=false, allow_git_helpers=false)

0 commit comments

Comments
 (0)