• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37431

pending completion
#37431

push

local

web-flow
🤖 [master] Bump the Pkg stdlib from 3ac94b211 to ed505db0b (#48528)

72309 of 77895 relevant lines covered (92.83%)

33191018.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.45
/stdlib/LibGit2/src/callbacks.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
"""Mirror callback function
4

5
Function sets `+refs/*:refs/*` refspecs and `mirror` flag for remote reference.
6
"""
7
function mirror_callback(remote::Ptr{Ptr{Cvoid}}, repo_ptr::Ptr{Cvoid},
1✔
8
                         name::Cstring, url::Cstring, payload::Ptr{Cvoid})
9
    ensure_initialized()
1✔
10
    # Create the remote with a mirroring url
11
    fetch_spec = "+refs/*:refs/*"
1✔
12
    err = ccall((:git_remote_create_with_fetchspec, :libgit2), Cint,
1✔
13
                (Ptr{Ptr{Cvoid}}, Ptr{Cvoid}, Cstring, Cstring, Cstring),
14
                remote, repo_ptr, name, url, fetch_spec)
15
    err != 0 && return Cint(err)
1✔
16

17
    # And set the configuration option to true for the push command
18
    config = GitConfig(GitRepo(repo_ptr,false))
1✔
19
    name_str = unsafe_string(name)
1✔
20
    err= try set!(config, "remote.$name_str.mirror", true)
2✔
21
         catch; -1
1✔
22
         finally close(config)
2✔
23
         end
24
    err != 0 && return Cint(err)
1✔
25
    return Cint(0)
1✔
26
end
27

28
"""
29
    LibGit2.is_passphrase_required(private_key) -> Bool
30

31
Return `true` if the `private_key` file requires a passphrase, `false` otherwise.
32
"""
33
function is_passphrase_required(private_key::AbstractString)
5✔
34
    !isfile(private_key) && return false
8✔
35

36
    # In encrypted private keys, the second line is "Proc-Type: 4,ENCRYPTED"
37
    return open(private_key) do f
6✔
38
        readline(f)
6✔
39
        readline(f) == "Proc-Type: 4,ENCRYPTED"
6✔
40
    end
41
end
42

43
function user_abort()
1✔
44
    ensure_initialized()
1✔
45
    # Note: Potentially it could be better to just throw a Julia error.
46
    ccall((:giterr_set_str, :libgit2), Cvoid,
1✔
47
          (Cint, Cstring), Cint(Error.Callback),
48
          "Aborting, user cancelled credential request.")
49
    return Cint(Error.EUSER)
1✔
50
end
51

52
function prompt_limit()
×
53
    ensure_initialized()
×
54
    ccall((:giterr_set_str, :libgit2), Cvoid,
×
55
          (Cint, Cstring), Cint(Error.Callback),
56
          "Aborting, maximum number of prompts reached.")
57
    return Cint(Error.EAUTH)
×
58
end
59

60
function exhausted_abort()
5✔
61
    ensure_initialized()
5✔
62
    ccall((:giterr_set_str, :libgit2), Cvoid,
5✔
63
          (Cint, Cstring), Cint(Error.Callback),
64
          "All authentication methods have failed.")
65
    return Cint(Error.EAUTH)
5✔
66
end
67

68
function authenticate_ssh(libgit2credptr::Ptr{Ptr{Cvoid}}, p::CredentialPayload, username_ptr)
5✔
69
    ensure_initialized()
5✔
70
    cred = p.credential::SSHCredential
5✔
71
    revised = false
×
72

73
    # Use a filled credential as-is on the first pass. Reset password on successive calls.
74
    if p.first_pass && isfilled(cred)
6✔
75
        revised = true
2✔
76
    elseif !p.first_pass
3✔
77
        cred.pass = ""
2✔
78
    end
79

80
    # first try ssh-agent if credentials support its usage
81
    if p.use_ssh_agent && username_ptr != Cstring(C_NULL) && (!revised || !isfilled(cred))
6✔
82
        err = ccall((:git_cred_ssh_key_from_agent, :libgit2), Cint,
×
83
                    (Ptr{Ptr{Cvoid}}, Cstring), libgit2credptr, username_ptr)
84

85
        p.use_ssh_agent = false  # use ssh-agent only one time
×
86
        err == 0 && return Cint(0)
×
87
    end
88

89
    if p.use_env && (!revised || !isfilled(cred))
7✔
90
        if isempty(cred.user) && username_ptr != Cstring(C_NULL)
2✔
91
            cred.user = unsafe_string(username_ptr)
×
92
        end
93

94
        if haskey(ENV, "SSH_KEY_PATH")
2✔
95
            cred.prvkey = ENV["SSH_KEY_PATH"]
×
96
        elseif isempty(cred.prvkey)
2✔
97
            for keytype in ("rsa", "ecdsa")
1✔
98
                private_key_file = joinpath(homedir(), ".ssh", "id_$keytype")
2✔
99
                if isfile(private_key_file)
2✔
100
                    cred.prvkey = private_key_file
×
101
                    break
×
102
                end
103
            end
2✔
104
        end
105

106
        cred.pubkey = Base.get(ENV, "SSH_PUB_KEY_PATH") do
2✔
107
            default = cred.prvkey * ".pub"
2✔
108
            if isempty(cred.pubkey) && isfile(default)
2✔
109
                default
×
110
            else
111
                cred.pubkey
2✔
112
            end
113
        end
114

115
        cred.pass = Base.get(ENV, "SSH_KEY_PASS", cred.pass)
2✔
116

117
        revised = true
×
118
        p.use_env = false
2✔
119
    end
120

121
    if p.remaining_prompts > 0 && (!revised || !isfilled(cred))
7✔
122
        if isempty(cred.user) || username_ptr == Cstring(C_NULL)
2✔
123
            url = git_url(scheme=p.scheme, host=p.host)
×
124
            response = Base.prompt("Username for '$url'", default=cred.user)
×
125
            response === nothing && return user_abort()
×
126
            cred.user = response
×
127
        end
128

129
        url = git_url(scheme=p.scheme, host=p.host, username=cred.user)
1✔
130

131
        # For SSH we need a private key location
132
        last_private_key = cred.prvkey
1✔
133
        if !isfile(cred.prvkey) || !revised || !haskey(ENV, "SSH_KEY_PATH")
1✔
134
            response = Base.prompt("Private key location for '$url'", default=cred.prvkey)
1✔
135
            response === nothing && return user_abort()
1✔
136
            cred.prvkey = expanduser(response)
2✔
137

138
            # Only update the public key if the private key changed
139
            if cred.prvkey != last_private_key
1✔
140
                cred.pubkey = cred.prvkey * ".pub"
1✔
141
            end
142
        end
143

144
        # For SSH we need a public key location. Avoid asking about the public key as
145
        # typically this will just annoy users.
146
        stale = !p.first_pass && cred.prvkey == last_private_key && cred.pubkey != cred.prvkey * ".pub"
1✔
147
        if isfile(cred.prvkey) && (stale || !isfile(cred.pubkey))
2✔
148
            response = Base.prompt("Public key location for '$url'", default=cred.pubkey)
×
149
            response === nothing && return user_abort()
×
150
            cred.pubkey = expanduser(response)
×
151
        end
152

153
        # Ask for a passphrase when the private key exists and requires a passphrase
154
        if isempty(cred.pass) && is_passphrase_required(cred.prvkey)
2✔
155
            if Sys.iswindows()
×
156
                response = Base.winprompt(
×
157
                    "Your SSH Key requires a password, please enter it now:",
158
                    "Passphrase required", cred.prvkey; prompt_username=false)
159
                response === nothing && return user_abort()
×
160
                cred.pass = response[2]
×
161
            else
162
                response = Base.getpass("Passphrase for $(cred.prvkey)")
×
163
                response === nothing && return user_abort()
×
164
                cred.pass = response
×
165
                isempty(cred.pass) && return user_abort()  # Ambiguous if EOF or newline
×
166
            end
167
        end
168

169
        revised = true
×
170

171
        p.remaining_prompts -= 1
1✔
172
        p.remaining_prompts <= 0 && return prompt_limit()
1✔
173
    end
174

175
    if !revised
5✔
176
        return exhausted_abort()
1✔
177
    end
178
    return ccall((:git_cred_ssh_key_new, :libgit2), Cint,
4✔
179
                 (Ptr{Ptr{Cvoid}}, Cstring, Cstring, Cstring, Cstring),
180
                 libgit2credptr, cred.user, cred.pubkey, cred.prvkey, cred.pass)
181
end
182

183
function authenticate_userpass(libgit2credptr::Ptr{Ptr{Cvoid}}, p::CredentialPayload)
20✔
184
    ensure_initialized()
20✔
185
    cred = p.credential::UserPasswordCredential
20✔
186
    revised = false
×
187

188
    # Use a filled credential as-is on the first pass. Reset password on successive calls.
189
    if p.first_pass && isfilled(cred)
26✔
190
        revised = true
8✔
191
    elseif !p.first_pass
12✔
192
        cred.pass = ""
6✔
193
    end
194

195
    if p.use_git_helpers && (!revised || !isfilled(cred))
20✔
196
        git_cred = GitCredential(p.config, p.url)
2✔
197

198
         # Use `deepcopy` to ensure shredding the `git_cred` does not shred the `cred`s copy
199
        cred.user = something(git_cred.username, "")
4✔
200
        cred.pass = deepcopy(something(git_cred.password, ""))
3✔
201
        Base.shred!(git_cred)
2✔
202
        revised = true
×
203

204
        p.use_git_helpers = false
2✔
205
    end
206

207
    if p.remaining_prompts > 0 && (!revised || !isfilled(cred))
27✔
208
        url = git_url(scheme=p.scheme, host=p.host)
7✔
209
        username = isempty(cred.user) ? p.username : cred.user
11✔
210
        if Sys.iswindows()
×
211
            response = Base.winprompt(
×
212
                "Please enter your credentials for '$url'", "Credentials required",
213
                username; prompt_username=true)
214
            response === nothing && return user_abort()
×
215
            cred.user, cred.pass = response
×
216
        else
217
            response = Base.prompt("Username for '$url'", default=username)
7✔
218
            response === nothing && return user_abort()
7✔
219
            cred.user = response
7✔
220

221
            url = git_url(scheme=p.scheme, host=p.host, username=cred.user)
6✔
222
            response = Base.getpass("Password for '$url'")
6✔
223
            response === nothing && return user_abort()
×
224
            cred.pass = response
6✔
225
            isempty(cred.pass) && return user_abort()  # Ambiguous if EOF or newline
6✔
226
        end
227

228
        revised = true
×
229

230
        p.remaining_prompts -= 1
6✔
231
        p.remaining_prompts <= 0 && return prompt_limit()
6✔
232
    end
233

234
    if !revised
19✔
235
        return exhausted_abort()
4✔
236
    end
237

238
    return ccall((:git_cred_userpass_plaintext_new, :libgit2), Cint,
15✔
239
                 (Ptr{Ptr{Cvoid}}, Cstring, Cstring),
240
                 libgit2credptr, cred.user, cred.pass)
241
end
242

243

244
"""
245
    credential_callback(...) -> Cint
246

247
A LibGit2 credential callback function which provides different credential acquisition
248
functionality w.r.t. a connection protocol. The `payload_ptr` is required to contain a
249
`LibGit2.CredentialPayload` object which will keep track of state and settings.
250

251
The `allowed_types` contains a bitmask of `LibGit2.Consts.GIT_CREDTYPE` values specifying
252
which authentication methods should be attempted.
253

254
Credential authentication is done in the following order (if supported):
255
- SSH agent
256
- SSH private/public key pair
257
- Username/password plain text
258

259
If a user is presented with a credential prompt they can abort the prompt by typing `^D`
260
(pressing the control key together with the `d` key).
261

262
**Note**: Due to the specifics of the `libgit2` authentication procedure, when
263
authentication fails, this function is called again without any indication whether
264
authentication was successful or not. To avoid an infinite loop from repeatedly
265
using the same faulty credentials, we will keep track of state using the payload.
266

267
For addition details see the LibGit2 guide on
268
[authenticating against a server](https://libgit2.org/docs/guides/authentication/).
269
"""
270
function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring,
27✔
271
                              username_ptr::Cstring, allowed_types::Cuint,
272
                              p::CredentialPayload)
273
    err = Cint(0)
×
274

275
    # Parse URL only during the first call to this function. Future calls will use the
276
    # information cached inside the payload.
277
    if isempty(p.url)
27✔
278
        p.url = unsafe_string(url_ptr)
19✔
279
        m = match(URL_REGEX, p.url)::RegexMatch
19✔
280

281
        p.scheme = something(m[:scheme], SubString(""))
34✔
282
        p.username = something(m[:user], SubString(""))
22✔
283
        p.host = something(m[:host])
38✔
284

285
        # When an explicit credential is supplied we will make sure to use the given
286
        # credential during the first callback by modifying the allowed types. The
287
        # modification only is in effect for the first callback since `allowed_types` cannot
288
        # be mutated.
289
        cache = p.cache
19✔
290
        explicit = p.explicit
19✔
291
        if explicit !== nothing
19✔
292
            cred = explicit
9✔
293

294
            # Copy explicit credentials to avoid mutating approved credentials.
295
            # invalidation fix from cred being non-inferrable
296
            p.credential = Base.invokelatest(deepcopy, cred)
9✔
297

298
            if isa(cred, SSHCredential)
9✔
299
                allowed_types &= Cuint(Consts.CREDTYPE_SSH_KEY)
3✔
300
            elseif isa(cred, UserPasswordCredential)
6✔
301
                allowed_types &= Cuint(Consts.CREDTYPE_USERPASS_PLAINTEXT)
6✔
302
            else
303
                allowed_types &= Cuint(0)  # Unhandled credential type
9✔
304
            end
305
        elseif cache !== nothing
10✔
306
            cred_id = credential_identifier(p.scheme, p.host)
5✔
307

308
            # Perform a deepcopy as we do not want to mutate approved cached credentials
309
            if haskey(cache, cred_id)
5✔
310
                # invalidation fix from cache[cred_id] being non-inferrable
311
                p.credential = Base.invokelatest(deepcopy, cache[cred_id])
4✔
312
            end
313
        end
314
        p.first_pass = true
19✔
315
    else
316
        p.first_pass = false
8✔
317
    end
318

319
    # use ssh key or ssh-agent
320
    if isset(allowed_types, Cuint(Consts.CREDTYPE_SSH_KEY))
27✔
321
        if p.credential === nothing || !isa(p.credential, SSHCredential)
9✔
322
            p.credential = SSHCredential(p.username)
1✔
323
        end
324
        err = authenticate_ssh(libgit2credptr, p, username_ptr)
5✔
325
        err == 0 && return err
5✔
326
    end
327

328
    if isset(allowed_types, Cuint(Consts.CREDTYPE_USERPASS_PLAINTEXT))
23✔
329
        if p.credential === nothing || !isa(p.credential, UserPasswordCredential)
35✔
330
            p.credential = UserPasswordCredential(p.username)
5✔
331
        end
332
        err = authenticate_userpass(libgit2credptr, p)
20✔
333
        err == 0 && return err
20✔
334
    end
335

336
    # No authentication method we support succeeded. The most likely cause is
337
    # that explicit credentials were passed in, but said credentials are incompatible
338
    # with the requested authentication method.
339
    if err == 0
8✔
340
        if p.explicit !== nothing
2✔
341
            ensure_initialized()
2✔
342
            ccall((:giterr_set_str, :libgit2), Cvoid, (Cint, Cstring), Cint(Error.Callback),
2✔
343
                  "The explicitly provided credential is incompatible with the requested " *
344
                  "authentication methods.")
345
        end
346
        err = Cint(Error.EAUTH)
×
347
    end
348
    return err
8✔
349
end
350

351
function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring,
3✔
352
                              username_ptr::Cstring, allowed_types::Cuint,
353
                              payloads::Dict{Symbol, Any})
354
    p = payloads[:credentials]
3✔
355
    return credentials_callback(libgit2credptr, url_ptr, username_ptr, allowed_types, p)
3✔
356
end
357

358
function fetchhead_foreach_callback(ref_name::Cstring, remote_url::Cstring,
6✔
359
                                    oid_ptr::Ptr{GitHash}, is_merge::Cuint, payload::Any)
360
    fhead_vec = payload::Vector{FetchHead}
6✔
361
    Base.push!(fhead_vec, FetchHead(unsafe_string(ref_name), unsafe_string(remote_url),
6✔
362
        unsafe_load(oid_ptr), is_merge == 1))
363
    return Cint(0)
6✔
364
end
365

366
struct CertHostKey
367
    parent  :: Cint
368
    mask    :: Cint
369
    md5     :: NTuple{16,UInt8}
370
    sha1    :: NTuple{20,UInt8}
371
    sha256  :: NTuple{32,UInt8}
372
    type    :: Cint
373
    hostkey :: Ptr{Cchar}
374
    len     :: Csize_t
375
end
376

377
function verify_host_error(message::AbstractString)
2✔
378
    printstyled(stderr, "$message\n", color = :cyan, bold = true)
2✔
379
end
380

381
function certificate_callback(
7✔
382
    cert_p :: Ptr{CertHostKey},
383
    valid  :: Cint,
384
    host_p :: Ptr{Cchar},
385
    data_p :: Ptr{Cvoid},
386
)::Cint
387
    valid != 0 && return Consts.CERT_ACCEPT
7✔
388
    host = unsafe_string(host_p)
2✔
389
    cert_type = unsafe_load(convert(Ptr{Cint}, cert_p))
2✔
390
    transport = cert_type == Consts.CERT_TYPE_TLS ? "TLS" :
2✔
391
                cert_type == Consts.CERT_TYPE_SSH ? "SSH" : nothing
392
    if !NetworkOptions.verify_host(host, transport)
4✔
393
        # user has opted out of host verification
394
        return Consts.CERT_ACCEPT
×
395
    end
396
    if transport == "TLS"
4✔
397
        # TLS verification is done before the callback and indicated with the
398
        # incoming `valid` flag, so if we get here then host verification failed
399
        verify_host_error("TLS host verification: the identity of the server `$host` could not be verified. Someone could be trying to man-in-the-middle your connection. It is also possible that the correct server is using an invalid certificate or that your system's certificate authority root store is misconfigured.")
2✔
400
        return Consts.CERT_REJECT
2✔
401
    elseif transport == "SSH"
×
402
        # SSH verification has to be done here
403
        files = NetworkOptions.ssh_known_hosts_files()
×
404
        cert = unsafe_load(cert_p)
×
405
        check = ssh_knownhost_check(files, host, cert)
×
406
        valid = false
×
407
        if check == Consts.LIBSSH2_KNOWNHOST_CHECK_MATCH
×
408
            valid = true
×
409
        elseif check == Consts.LIBSSH2_KNOWNHOST_CHECK_NOTFOUND
×
410
            if Sys.which("ssh-keyscan") !== nothing
×
411
                msg = "Please run `ssh-keyscan $host >> $(files[1])` in order to add the server to your known hosts file and then try again."
×
412
            else
413
                msg = "Please connect once using `ssh $host` in order to add the server to your known hosts file and then try again. You may not be allowed to log in (wrong user and/or no login allowed), but ssh will prompt you to add a host key for the server which will allow libgit2 to verify the server."
×
414
            end
415
            verify_host_error("SSH host verification: the server `$host` is not a known host. $msg")
×
416
        elseif check == Consts.LIBSSH2_KNOWNHOST_CHECK_MISMATCH
×
417
            verify_host_error("SSH host verification: the identity of the server `$host` does not match its known hosts record. Someone could be trying to man-in-the-middle your connection. It is also possible that the server has changed its key, in which case you should check with the server administrator and if they confirm that the key has been changed, update your known hosts file.")
×
418
        else
419
            @error("unexpected SSH known host check result", check)
×
420
        end
421
        return valid ? Consts.CERT_ACCEPT : Consts.CERT_REJECT
×
422
    end
423
    @error("unexpected transport encountered, refusing to validate", cert_type)
×
424
    return Consts.CERT_REJECT
×
425
end
426

427
struct KnownHost
428
    magic :: Cuint
429
    node  :: Ptr{Cvoid}
430
    name  :: Ptr{Cchar}
431
    key   :: Ptr{Cchar}
432
    type  :: Cint
433
end
434

435
function ssh_knownhost_check(
×
436
    files :: AbstractVector{<:AbstractString},
437
    host  :: AbstractString,
438
    cert  :: CertHostKey,
439
)
440
    key = unsafe_wrap(Array, cert.hostkey, cert.len)
×
441
    return ssh_knownhost_check(files, host, key)
×
442
end
443

444
function ssh_knownhost_check(
29✔
445
    files :: AbstractVector{<:AbstractString},
446
    host  :: AbstractString,
447
    key   :: Vector{Cchar},
448
)
449
    if (m = match(r"^(.+):(\d+)$", host)) !== nothing
29✔
450
        host = m.captures[1]
×
451
        port = parse(Int, something(m.captures[2]))
×
452
    else
453
        port = 22 # default SSH port
×
454
    end
455
    len = length(key)
29✔
456
    mask = Consts.LIBSSH2_KNOWNHOST_TYPE_PLAIN |
×
457
           Consts.LIBSSH2_KNOWNHOST_KEYENC_RAW
458
    session = @ccall "libssh2".libssh2_session_init_ex(
29✔
459
        C_NULL :: Ptr{Cvoid},
460
        C_NULL :: Ptr{Cvoid},
461
        C_NULL :: Ptr{Cvoid},
462
        C_NULL :: Ptr{Cvoid},
463
    ) :: Ptr{Cvoid}
464
    for file in files
29✔
465
        ispath(file) || continue
33✔
466
        hosts = @ccall "libssh2".libssh2_knownhost_init(
28✔
467
            session :: Ptr{Cvoid},
468
        ) :: Ptr{Cvoid}
469
        count = @ccall "libssh2".libssh2_knownhost_readfile(
28✔
470
            hosts :: Ptr{Cvoid},
471
            file  :: Cstring,
472
            1     :: Cint, # standard OpenSSH format
473
        ) :: Cint
474
        if count < 0
28✔
475
            @warn("Error parsing SSH known hosts file `$file`")
×
476
            @ccall "libssh2".libssh2_knownhost_free(hosts::Ptr{Cvoid})::Cvoid
×
477
            continue
×
478
        end
479
        check = @ccall "libssh2".libssh2_knownhost_checkp(
28✔
480
            hosts  :: Ptr{Cvoid},
481
            host   :: Cstring,
482
            port   :: Cint,
483
            key    :: Ptr{Cchar},
484
            len    :: Csize_t,
485
            mask   :: Cint,
486
            C_NULL :: Ptr{Ptr{KnownHost}},
487
        ) :: Cint
488
        if check == Consts.LIBSSH2_KNOWNHOST_CHECK_MATCH ||
48✔
489
            check == Consts.LIBSSH2_KNOWNHOST_CHECK_MISMATCH
490
            @ccall "libssh2".libssh2_knownhost_free(hosts::Ptr{Cvoid})::Cvoid
16✔
491
            @assert 0 == @ccall "libssh2".libssh2_session_free(session::Ptr{Cvoid})::Cint
16✔
492
            return check
16✔
493
        else
494
            @ccall "libssh2".libssh2_knownhost_free(hosts::Ptr{Cvoid})::Cvoid
12✔
495
            if check == Consts.LIBSSH2_KNOWNHOST_CHECK_FAILURE
12✔
496
                @warn("Error searching SSH known hosts file `$file`")
×
497
            end
498
            continue
12✔
499
        end
500
    end
30✔
501
    # name not found in any known hosts files
502
    @assert 0 == @ccall "libssh2".libssh2_session_free(session::Ptr{Cvoid})::Cint
13✔
503
    return Consts.LIBSSH2_KNOWNHOST_CHECK_NOTFOUND
13✔
504
end
505

506
function trace_callback(level::Cint, msg::Cstring)::Cint
×
507
    println(stderr, "[$level]: $(unsafe_string(msg))")
×
508
    return 0
×
509
end
510

511
"C function pointer for `mirror_callback`"
512
mirror_cb() = @cfunction(mirror_callback, Cint, (Ptr{Ptr{Cvoid}}, Ptr{Cvoid}, Cstring, Cstring, Ptr{Cvoid}))
1✔
513
"C function pointer for `credentials_callback`"
514
credentials_cb() = @cfunction(credentials_callback, Cint, (Ptr{Ptr{Cvoid}}, Cstring, Cstring, Cuint, Any))
35✔
515
"C function pointer for `fetchhead_foreach_callback`"
516
fetchhead_foreach_cb() = @cfunction(fetchhead_foreach_callback, Cint, (Cstring, Cstring, Ptr{GitHash}, Cuint, Any))
2✔
517
"C function pointer for `certificate_callback`"
518
certificate_cb() = @cfunction(certificate_callback, Cint, (Ptr{CertHostKey}, Cint, Ptr{Cchar}, Ptr{Cvoid}))
36✔
519
"C function pointer for `trace_callback`"
520
trace_cb() = @cfunction(trace_callback, Cint, (Cint, Cstring))
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc