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

JuliaLang / julia / #37489

pending completion
#37489

push

local

web-flow
fix `obviously_disjoint` for Union Types (#49177)

70007 of 82924 relevant lines covered (84.42%)

33257777.23 hits per line

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

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

3
const GIT_CRED_ATTRIBUTES = ("protocol", "host", "path", "username", "password", "url")
4

5
"""
6
    GitCredential
7

8
Git credential information used in communication with git credential helpers. The field are
9
named using the [input/output key specification](https://git-scm.com/docs/git-credential#IOFMT).
10
"""
11
mutable struct GitCredential
12
    protocol::Union{String, Nothing}
13
    host::Union{String, Nothing}
14
    path::Union{String, Nothing}
15
    username::Union{String, Nothing}
16
    password::Union{Base.SecretBuffer, Nothing}
17
    use_http_path::Bool
18

19
    function GitCredential(
66✔
20
            protocol::Union{AbstractString, Nothing}=nothing,
21
            host::Union{AbstractString, Nothing}=nothing,
22
            path::Union{AbstractString, Nothing}=nothing,
23
            username::Union{AbstractString, Nothing}=nothing,
24
            password::Union{AbstractString, Nothing}=nothing)
25
        new(protocol, host, path, username, password, true)
66✔
26
    end
27
end
28

29
function GitCredential(cfg::GitConfig, url::AbstractString)
×
30
    fill!(cfg, parse(GitCredential, url))
×
31
end
32

33
function GitCredential(user_pass_cred::UserPasswordCredential, url::AbstractString)
1✔
34
    cred = parse(GitCredential, url)
3✔
35
    cred.username = user_pass_cred.user
3✔
36
    cred.password = deepcopy(user_pass_cred.pass)
3✔
37
    return cred
1✔
38
end
39

40
Base.:(==)(c1::GitCredential, c2::GitCredential) = (c1.protocol, c1.host, c1.path, c1.username, c1.password, c1.use_http_path) ==
26✔
41
                                                   (c2.protocol, c2.host, c2.path, c2.username, c2.password, c2.use_http_path)
42
Base.hash(cred::GitCredential, h::UInt) = hash(GitCredential, hash((cred.protocol, cred.host, cred.path, cred.username, cred.password, cred.use_http_path), h))
×
43

44
function Base.shred!(cred::GitCredential)
56✔
45
    cred.protocol = nothing
60✔
46
    cred.host = nothing
60✔
47
    cred.path = nothing
60✔
48
    cred.username = nothing
60✔
49
    pwd = cred.password
60✔
50
    pwd !== nothing && Base.shred!(pwd)
60✔
51
    cred.password = nothing
60✔
52
    return cred
60✔
53
end
54

55

56
"""
57
    ismatch(url, git_cred) -> Bool
58

59
Checks if the `git_cred` is valid for the given `url`.
60
"""
61
function ismatch(url::AbstractString, git_cred::GitCredential)
29✔
62
    isempty(url) && return true
29✔
63

64
    m = match(URL_REGEX, url)
18✔
65
    m === nothing && error("Unable to parse URL")
18✔
66

67
    # Note: missing URL groups match anything
68
    (m[:scheme] === nothing ? true : m[:scheme] == git_cred.protocol) &&
18✔
69
    (m[:host] === nothing ? true : m[:host] == git_cred.host) &&
70
    (m[:path] === nothing ? true : m[:path] == git_cred.path) &&
71
    (m[:user] === nothing ? true : m[:user] == git_cred.username)
72
end
73

74
function isfilled(cred::GitCredential)
×
75
    cred.username !== nothing && cred.password !== nothing
×
76
end
77

78
function Base.parse(::Type{GitCredential}, url::AbstractString)
4✔
79
    m = match(URL_REGEX, url)
4✔
80
    m === nothing && error("Unable to parse URL")
4✔
81
    return GitCredential(
4✔
82
        m[:scheme],
83
        m[:host],
84
        m[:path],
85
        m[:user],
86
        m[:password],
87
    )
88
end
89

90
function Base.copy!(a::GitCredential, b::GitCredential)
×
91
    Base.shred!(a)
1✔
92
    a.protocol = b.protocol
2✔
93
    a.host = b.host
2✔
94
    a.path = b.path
2✔
95
    a.username = b.username
2✔
96
    a.password = b.password === nothing ? nothing : copy(b.password)
1✔
97
    return a
1✔
98
end
99

100
function Base.write(io::IO, cred::GitCredential)
31✔
101
    cred.protocol !== nothing && write(io, "protocol=", cred.protocol, '\n')
61✔
102
    cred.host !== nothing && write(io, "host=", cred.host, '\n')
61✔
103
    cred.path !== nothing && cred.use_http_path && write(io, "path=", cred.path, '\n')
45✔
104
    cred.username !== nothing && write(io, "username=", cred.username, '\n')
43✔
105
    cred.password !== nothing && write(io, "password=", cred.password, '\n')
42✔
106
    nothing
31✔
107
end
108

109
function Base.read!(io::IO, cred::GitCredential)
31✔
110
    # https://git-scm.com/docs/git-credential#IOFMT
111
    while !(eof(io)::Bool)
73✔
112
        key::AbstractString = readuntil(io, '=')
42✔
113
        if key == "password"
42✔
114
            value = Base.SecretBuffer()
14✔
115
            while !(eof(io)::Bool) && (c = read(io, UInt8)) != UInt8('\n')
74✔
116
                write(value, c)
60✔
117
            end
60✔
118
            seekstart(value)
14✔
119
        else
120
            value = readuntil(io, '\n')
28✔
121
        end
122

123
        if key == "url"
42✔
124
            # Any components which are missing from the URL will be set to empty
125
            # https://git-scm.com/docs/git-credential#git-credential-codeurlcode
126
            Base.shred!(parse(GitCredential, value::AbstractString)) do urlcred
1✔
127
                copy!(cred, urlcred)
1✔
128
            end
129
        elseif key in GIT_CRED_ATTRIBUTES
160✔
130
            field = getproperty(cred, Symbol(key))
39✔
131
            field !== nothing && Symbol(key) === :password && Base.shred!(field)
39✔
132
            setproperty!(cred, Symbol(key), value)
39✔
133
        elseif !all(isspace, key)
2✔
134
            @warn "Unknown git credential attribute found: $(repr(key))"
1✔
135
        end
136
    end
42✔
137

138
    return cred
31✔
139
end
140

141
function fill!(cfg::GitConfig, cred::GitCredential)
×
142
    cred.use_http_path = use_http_path(cfg, cred)
×
143

144
    # When the username is missing default to using the username set in the configuration
145
    if cred.username === nothing
×
146
        cred.username = default_username(cfg, cred)
×
147
    end
148

149
    for helper in credential_helpers(cfg, cred)
×
150
        fill!(helper, cred)
×
151

152
        # "Once Git has acquired both a username and a password, no more helpers will be
153
        # tried." – https://git-scm.com/docs/gitcredentials#gitcredentials-helper
154
        !isfilled(cred) && break
×
155
    end
×
156

157
    return cred
×
158
end
159

160
struct GitCredentialHelper
161
    cmd::Cmd
17✔
162
end
163

164
function Base.parse(::Type{GitCredentialHelper}, helper::AbstractString)
12✔
165
    # The helper string can take on different behaviors depending on the value:
166
    # - "Code after `!` evaluated in shell" – https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage
167
    # - "If the helper name is not an absolute path, then the string `git credential-` is
168
    #   prepended." – https://git-scm.com/docs/gitcredentials#gitcredentials-helper
169
    if startswith(helper, '!')
12✔
170
        cmd_str = helper[2:end]
10✔
171
    elseif isabspath(first(Base.shell_split(helper)))
7✔
172
        cmd_str = helper
1✔
173
    else
174
        cmd_str = "git credential-$helper"
6✔
175
    end
176

177
    GitCredentialHelper(`$(Base.shell_split(cmd_str))`)
12✔
178
end
179

180
function Base.:(==)(a::GitCredentialHelper, b::GitCredentialHelper)
6✔
181
    a.cmd == b.cmd
6✔
182
end
183

184
function run!(helper::GitCredentialHelper, operation::AbstractString, cred::GitCredential)
25✔
185
    cmd = `$(helper.cmd) $operation`
25✔
186
    p = open(cmd, "r+")
25✔
187

188
    # Provide the helper with the credential information we know
189
    write(p, cred)
25✔
190
    write(p, "\n")
25✔
191
    t = @async close(p.in)
25✔
192

193
    # Process the response from the helper
194
    Base.read!(p, cred)
25✔
195
    wait(p)
25✔
196

197
    return cred
25✔
198
end
199

200
function run(helper::GitCredentialHelper, operation::AbstractString, cred::GitCredential)
×
201
    run!(helper, operation, deepcopy(cred))
7✔
202
end
203

204
# The available actions between using `git credential` and helpers are slightly different.
205
# We will directly interact with the helpers as that way we can request credential
206
# information without a prompt (helper `get` vs. git credential `fill`).
207
# https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage
208

209
fill!(helper::GitCredentialHelper, cred::GitCredential) = run!(helper, "get", cred)
18✔
210
approve(helper::GitCredentialHelper, cred::GitCredential) = run(helper, "store", cred)
4✔
211
reject(helper::GitCredentialHelper, cred::GitCredential) = run(helper, "erase", cred)
3✔
212

213
"""
214
    credential_helpers(config, git_cred) -> Vector{GitCredentialHelper}
215

216
Return all of the `GitCredentialHelper`s found within the provided `config` which are valid
217
for the specified `git_cred`.
218
"""
219
function credential_helpers(cfg::GitConfig, cred::GitCredential)
5✔
220
    helpers = GitCredentialHelper[]
5✔
221

222
    # https://git-scm.com/docs/gitcredentials#gitcredentials-helper
223
    for entry in GitConfigIter(cfg, r"credential.*\.helper$")
5✔
224
        section, url, name, value = split_cfg_entry(entry)
9✔
225
        @assert name == "helper"
9✔
226

227
        # Only use configuration settings where the URL applies to the git credential
228
        ismatch(url, cred) || continue
9✔
229

230
        # An empty credential.helper resets the list to empty
231
        if isempty(value)
8✔
232
            empty!(helpers)
1✔
233
        else
234
            Base.push!(helpers, parse(GitCredentialHelper, value))
7✔
235
        end
236
    end
9✔
237

238
    return helpers
5✔
239
end
240

241
"""
242
    default_username(config, git_cred) -> Union{String, Nothing}
243

244
Return the default username, if any, provided by the `config` which is valid for the
245
specified `git_cred`.
246
"""
247
function default_username(cfg::GitConfig, cred::GitCredential)
7✔
248
    # https://git-scm.com/docs/gitcredentials#gitcredentials-username
249
    for entry in GitConfigIter(cfg, r"credential.*\.username")
7✔
250
        section, url, name, value = split_cfg_entry(entry)
8✔
251
        @assert name == "username"
8✔
252

253
        # Only use configuration settings where the URL applies to the git credential
254
        ismatch(url, cred) || continue
8✔
255
        return value
5✔
256
    end
3✔
257

258
    return nothing
2✔
259
end
260

261
function use_http_path(cfg::GitConfig, cred::GitCredential)
8✔
262
    seen_specific = false
×
263
    use_path = false  # Default is to ignore the path
×
264

265
    # https://git-scm.com/docs/gitcredentials#gitcredentials-useHttpPath
266
    #
267
    # Note: Ideally the regular expression should use "useHttpPath"
268
    # https://github.com/libgit2/libgit2/issues/4390
269
    for entry in GitConfigIter(cfg, r"credential.*\.usehttppath")
8✔
270
        section, url, name, value = split_cfg_entry(entry)
6✔
271

272
        # Ignore global configuration if we have already encountered more specific entry
273
        if ismatch(url, cred) && (!isempty(url) || !seen_specific)
8✔
274
            seen_specific = !isempty(url)
3✔
275
            use_path = value == "true"
3✔
276
        end
277
    end
6✔
278

279
    return use_path
8✔
280
end
281

282
approve(cfg::GitConfig, cred::AbstractCredential, url::AbstractString) = nothing
×
283
reject(cfg::GitConfig, cred::AbstractCredential, url::AbstractString) = nothing
×
284

285
function approve(cfg::GitConfig, cred::UserPasswordCredential, url::AbstractString)
1✔
286
    git_cred = GitCredential(cred, url)
1✔
287
    git_cred.use_http_path = use_http_path(cfg, git_cred)
1✔
288

289
    for helper in credential_helpers(cfg, git_cred)
1✔
290
        approve(helper, git_cred)
1✔
291
    end
2✔
292

293
    Base.shred!(git_cred)
1✔
294
    nothing
1✔
295
end
296

297
function reject(cfg::GitConfig, cred::UserPasswordCredential, url::AbstractString)
1✔
298
    git_cred = GitCredential(cred, url)
1✔
299
    git_cred.use_http_path = use_http_path(cfg, git_cred)
1✔
300

301
    for helper in credential_helpers(cfg, git_cred)
1✔
302
        reject(helper, git_cred)
1✔
303
    end
2✔
304

305
    Base.shred!(git_cred)
1✔
306
    nothing
1✔
307
end
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